| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| //===- bolt/Passes/ContinuityStats.cpp --------------------------*- 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This file implements the continuity stats calculation pass. | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "bolt/Passes/ContinuityStats.h" | ||
| #include "bolt/Core/BinaryBasicBlock.h" | ||
| #include "bolt/Core/BinaryFunction.h" | ||
| #include "bolt/Utils/CommandLineOpts.h" | ||
| #include "llvm/Support/CommandLine.h" | ||
| #include <queue> | ||
| #include <unordered_map> | ||
| #include <unordered_set> | ||
|
|
||
| #define DEBUG_TYPE "bolt-opts" | ||
|
|
||
| using namespace llvm; | ||
| using namespace bolt; | ||
|
|
||
| namespace opts { | ||
| extern cl::opt<unsigned> Verbosity; | ||
| cl::opt<unsigned> NumFunctionsForContinuityCheck( | ||
| "num-functions-for-continuity-check", | ||
| cl::desc("number of hottest functions to print aggregated " | ||
| "CFG discontinuity stats of."), | ||
| cl::init(1000), cl::ZeroOrMore, cl::Hidden, cl::cat(BoltOptCategory)); | ||
| } // namespace opts | ||
|
|
||
| namespace { | ||
| using FunctionListType = std::vector<const BinaryFunction *>; | ||
| using function_iterator = FunctionListType::iterator; | ||
|
|
||
| template <typename T> | ||
| void printDistribution(raw_ostream &OS, std::vector<T> &values, | ||
| bool Fraction = false) { | ||
| if (values.empty()) | ||
| return; | ||
| // Sort values from largest to smallest and print the MAX, TOP 1%, 5%, 10%, | ||
| // 20%, 50%, 80%, MIN. If Fraction is true, then values are printed as | ||
| // fractions instead of integers. | ||
| std::sort(values.begin(), values.end()); | ||
|
|
||
| auto printLine = [&](std::string Text, double Percent) { | ||
| int Rank = int(values.size() * (1.0 - Percent / 100)); | ||
| if (Percent == 0) | ||
| Rank = values.size() - 1; | ||
| if (Fraction) | ||
| OS << " " << Text << std::string(9 - Text.length(), ' ') << ": " | ||
| << format("%.2lf%%", values[Rank] * 100) << "\n"; | ||
| else | ||
| OS << " " << Text << std::string(9 - Text.length(), ' ') << ": " | ||
| << values[Rank] << "\n"; | ||
| }; | ||
|
|
||
| printLine("MAX", 0); | ||
| const int percentages[] = {1, 5, 10, 20, 50, 80}; | ||
| for (size_t i = 0; i < sizeof(percentages) / sizeof(percentages[0]); ++i) { | ||
| printLine("TOP " + std::to_string(percentages[i]) + "%", percentages[i]); | ||
| } | ||
| printLine("MIN", 100); | ||
| } | ||
|
|
||
| void printCFGContinuityStats(raw_ostream &OS, | ||
| iterator_range<function_iterator> &Functions) { | ||
| // Given a perfect profile, every positive-execution-count BB should be | ||
| // connected to an entry of the function through a positive-execution-count | ||
| // directed path in the control flow graph. | ||
| std::vector<size_t> NumUnreachables; | ||
| std::vector<size_t> SumECUnreachables; | ||
| std::vector<double> FractionECUnreachables; | ||
|
|
||
| for (auto it = Functions.begin(); it != Functions.end(); ++it) { | ||
| const BinaryFunction *Function = *it; | ||
| if (Function->size() <= 1) | ||
| continue; | ||
|
|
||
| // Compute the sum of all BB execution counts (ECs). | ||
| size_t NumPosECBBs = 0; | ||
| size_t SumAllBBEC = 0; | ||
| for (const BinaryBasicBlock &BB : *Function) { | ||
| const size_t BBEC = BB.getKnownExecutionCount(); | ||
| NumPosECBBs += BBEC > 0 ? 1 : 0; | ||
| SumAllBBEC += BBEC; | ||
| } | ||
|
|
||
| // Perform BFS on subgraph of CFG induced by positive weight edges. | ||
| // Compute the number of BBs reachable from the entry(s) of the function and | ||
| // the sum of their execution counts (ECs). | ||
| std::unordered_map<unsigned, const BinaryBasicBlock *> IndexToBB; | ||
| std::unordered_set<unsigned> Visited; | ||
| std::queue<unsigned> Queue; | ||
| for (const BinaryBasicBlock &BB : *Function) { | ||
| // Make sure BB.getIndex() is not already in IndexToBB. | ||
| assert(IndexToBB.find(BB.getIndex()) == IndexToBB.end()); | ||
| IndexToBB[BB.getIndex()] = &BB; | ||
| if (BB.isEntryPoint() && BB.getKnownExecutionCount() > 0) { | ||
| Queue.push(BB.getIndex()); | ||
| Visited.insert(BB.getIndex()); | ||
| } | ||
| } | ||
| while (!Queue.empty()) { | ||
| const unsigned BBIndex = Queue.front(); | ||
| const BinaryBasicBlock *BB = IndexToBB[BBIndex]; | ||
| Queue.pop(); | ||
| auto SuccBIIter = BB->branch_info_begin(); | ||
| for (const BinaryBasicBlock *Succ : BB->successors()) { | ||
| const uint64_t Count = SuccBIIter->Count; | ||
| if (Count == BinaryBasicBlock::COUNT_NO_PROFILE || Count == 0) { | ||
| ++SuccBIIter; | ||
| continue; | ||
| } | ||
| if (!Visited.insert(Succ->getIndex()).second) { | ||
| ++SuccBIIter; | ||
| continue; | ||
| } | ||
| Queue.push(Succ->getIndex()); | ||
| ++SuccBIIter; | ||
| } | ||
| } | ||
|
|
||
| const size_t NumReachableBBs = Visited.size(); | ||
|
|
||
| // Loop through Visited, and sum the corresponding BBs' execution counts | ||
| // (ECs). | ||
| size_t SumReachableBBEC = 0; | ||
| for (const unsigned BBIndex : Visited) { | ||
| const BinaryBasicBlock *BB = IndexToBB[BBIndex]; | ||
| SumReachableBBEC += BB->getKnownExecutionCount(); | ||
| } | ||
|
|
||
| const size_t NumPosECBBsUnreachableFromEntry = | ||
| NumPosECBBs - NumReachableBBs; | ||
| const size_t SumUnreachableBBEC = SumAllBBEC - SumReachableBBEC; | ||
| const double FractionECUnreachable = | ||
| (double)SumUnreachableBBEC / SumAllBBEC; | ||
|
|
||
| if (opts::Verbosity >= 2 && FractionECUnreachable >= 0.05) { | ||
| OS << "Non-trivial CFG discontinuity observed in function " | ||
| << Function->getPrintName() << "\n"; | ||
| LLVM_DEBUG(Function->dump()); | ||
| } | ||
|
|
||
| NumUnreachables.push_back(NumPosECBBsUnreachableFromEntry); | ||
| SumECUnreachables.push_back(SumUnreachableBBEC); | ||
| FractionECUnreachables.push_back(FractionECUnreachable); | ||
| } | ||
|
|
||
| if (FractionECUnreachables.empty()) | ||
| return; | ||
|
|
||
| std::sort(FractionECUnreachables.begin(), FractionECUnreachables.end()); | ||
| const int Rank = int(FractionECUnreachables.size() * 0.95); | ||
| OS << format("top 5%% function CFG discontinuity is %.2lf%%\n", | ||
| FractionECUnreachables[Rank] * 100); | ||
|
|
||
| if (opts::Verbosity >= 1) { | ||
| OS << "abbreviations: EC = execution count, POS BBs = positive EC BBs\n" | ||
| << "distribution of NUM(unreachable POS BBs) among all focal " | ||
| "functions\n"; | ||
| printDistribution(OS, NumUnreachables); | ||
|
|
||
| OS << "distribution of SUM_EC(unreachable POS BBs) among all focal " | ||
| "functions\n"; | ||
| printDistribution(OS, SumECUnreachables); | ||
|
|
||
| OS << "distribution of [(SUM_EC(unreachable POS BBs) / SUM_EC(all " | ||
| "POS BBs))] among all focal functions\n"; | ||
| printDistribution(OS, FractionECUnreachables, /*Fraction=*/true); | ||
| } | ||
| } | ||
|
|
||
| void printAll(BinaryContext &BC, FunctionListType &ValidFunctions, | ||
| size_t NumTopFunctions) { | ||
| // Sort the list of functions by execution counts (reverse). | ||
| llvm::sort(ValidFunctions, | ||
| [&](const BinaryFunction *A, const BinaryFunction *B) { | ||
| return A->getKnownExecutionCount() > B->getKnownExecutionCount(); | ||
| }); | ||
|
|
||
| const size_t RealNumTopFunctions = | ||
| std::min(NumTopFunctions, ValidFunctions.size()); | ||
|
|
||
| iterator_range<function_iterator> Functions( | ||
| ValidFunctions.begin(), ValidFunctions.begin() + RealNumTopFunctions); | ||
|
|
||
| BC.outs() << format("BOLT-INFO: among the hottest %zu functions ", | ||
| RealNumTopFunctions); | ||
| printCFGContinuityStats(BC.outs(), Functions); | ||
|
|
||
| // Print more detailed bucketed stats if requested. | ||
| if (opts::Verbosity >= 1 && RealNumTopFunctions >= 5) { | ||
| const size_t PerBucketSize = RealNumTopFunctions / 5; | ||
| BC.outs() << format( | ||
| "Detailed stats for 5 buckets, each with %zu functions:\n", | ||
| PerBucketSize); | ||
|
|
||
| // For each bucket, print the CFG continuity stats of the functions in the | ||
| // bucket. | ||
| for (size_t BucketIndex = 0; BucketIndex < 5; ++BucketIndex) { | ||
| const size_t StartIndex = BucketIndex * PerBucketSize; | ||
| const size_t EndIndex = StartIndex + PerBucketSize; | ||
| iterator_range<function_iterator> Functions( | ||
| ValidFunctions.begin() + StartIndex, | ||
| ValidFunctions.begin() + EndIndex); | ||
| const size_t MaxFunctionExecutionCount = | ||
| ValidFunctions[StartIndex]->getKnownExecutionCount(); | ||
| const size_t MinFunctionExecutionCount = | ||
| ValidFunctions[EndIndex - 1]->getKnownExecutionCount(); | ||
| BC.outs() << format("----------------\n| Bucket %zu: " | ||
| "|\n----------------\n", | ||
| BucketIndex + 1) | ||
| << format( | ||
| "execution counts of the %zu functions in the bucket: " | ||
| "%zu-%zu\n", | ||
| EndIndex - StartIndex, MinFunctionExecutionCount, | ||
| MaxFunctionExecutionCount); | ||
| printCFGContinuityStats(BC.outs(), Functions); | ||
| } | ||
| } | ||
| } | ||
| } // namespace | ||
|
|
||
| bool PrintContinuityStats::shouldOptimize(const BinaryFunction &BF) const { | ||
| if (BF.empty() || !BF.hasValidProfile()) | ||
| return false; | ||
|
|
||
| return BinaryFunctionPass::shouldOptimize(BF); | ||
| } | ||
|
|
||
| Error PrintContinuityStats::runOnFunctions(BinaryContext &BC) { | ||
| // Create a list of functions with valid profiles. | ||
| FunctionListType ValidFunctions; | ||
| for (const auto &BFI : BC.getBinaryFunctions()) { | ||
| const BinaryFunction *Function = &BFI.second; | ||
| if (PrintContinuityStats::shouldOptimize(*Function)) | ||
| ValidFunctions.push_back(Function); | ||
| } | ||
| if (ValidFunctions.empty() || opts::NumFunctionsForContinuityCheck == 0) | ||
| return Error::success(); | ||
|
|
||
| printAll(BC, ValidFunctions, opts::NumFunctionsForContinuityCheck); | ||
| return Error::success(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| ## Check that llvm-bolt will not unnecessarily relax ADR instruction. | ||
|
|
||
| # RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o | ||
| # RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -static | ||
| # RUN: llvm-bolt %t.exe -o %t.bolt --split-functions --split-strategy=random2 | ||
| # RUN: llvm-objdump -d --disassemble-symbols=_start %t.bolt | FileCheck %s | ||
| # RUN: llvm-objdump -d --disassemble-symbols=foo.cold.0 %t.bolt \ | ||
| # RUN: | FileCheck --check-prefix=CHECK-FOO %s | ||
|
|
||
| ## ADR below references its containing function that is split. But ADR is always | ||
| ## in the main fragment, thus there is no need to relax it. | ||
| .text | ||
| .globl _start | ||
| .type _start, %function | ||
| _start: | ||
| # CHECK: <_start>: | ||
| .cfi_startproc | ||
| adr x1, _start | ||
| # CHECK-NOT: adrp | ||
| # CHECK: adr | ||
| cmp x1, x11 | ||
| b.hi .L1 | ||
| mov x0, #0x0 | ||
| .L1: | ||
| ret x30 | ||
| .cfi_endproc | ||
| .size _start, .-_start | ||
|
|
||
|
|
||
| ## In foo, ADR is in the split fragment but references the main one. Thus, it | ||
| ## needs to be relaxed into ADRP + ADD. | ||
| .globl foo | ||
| .type foo, %function | ||
| foo: | ||
| .cfi_startproc | ||
| cmp x1, x11 | ||
| b.hi .L2 | ||
| mov x0, #0x0 | ||
| .L2: | ||
| # CHECK-FOO: <foo.cold.0>: | ||
| adr x1, foo | ||
| # CHECK-FOO: adrp | ||
| # CHECK-FOO-NEXT: add | ||
| ret x30 | ||
| .cfi_endproc | ||
| .size foo, .-foo | ||
|
|
||
| ## Force relocation mode. | ||
| .reloc 0, R_AARCH64_NONE |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ## Check profile discontinuity reporting | ||
| RUN: yaml2obj %p/Inputs/blarge_new.yaml &> %t.exe | ||
| RUN: llvm-bolt %t.exe -o %t.out --pa -p %p/Inputs/blarge_new.preagg.txt | FileCheck %s | ||
| CHECK: among the hottest 5 functions top 5% function CFG discontinuity is 100.00% |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| add_clang_library(clangIncludeFixerPlugin STATIC | ||
| IncludeFixerPlugin.cpp | ||
|
|
||
| LINK_LIBS | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| //===--- BitwisePointerCastCheck.cpp - clang-tidy -------------------------===// | ||
| // | ||
| // 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 "BitwisePointerCastCheck.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| void BitwisePointerCastCheck::registerMatchers(MatchFinder *Finder) { | ||
| if (getLangOpts().CPlusPlus20) { | ||
| auto IsPointerType = refersToType(qualType(isAnyPointer())); | ||
| Finder->addMatcher(callExpr(hasDeclaration(functionDecl(allOf( | ||
| hasName("::std::bit_cast"), | ||
| hasTemplateArgument(0, IsPointerType), | ||
| hasTemplateArgument(1, IsPointerType))))) | ||
| .bind("bit_cast"), | ||
| this); | ||
| } | ||
|
|
||
| auto IsDoublePointerType = | ||
| hasType(qualType(pointsTo(qualType(isAnyPointer())))); | ||
| Finder->addMatcher(callExpr(hasArgument(0, IsDoublePointerType), | ||
| hasArgument(1, IsDoublePointerType), | ||
| hasDeclaration(functionDecl(hasName("::memcpy")))) | ||
| .bind("memcpy"), | ||
| this); | ||
| } | ||
|
|
||
| void BitwisePointerCastCheck::check(const MatchFinder::MatchResult &Result) { | ||
| if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("bit_cast")) | ||
| diag(Call->getBeginLoc(), | ||
| "do not use 'std::bit_cast' to cast between pointers") | ||
| << Call->getSourceRange(); | ||
| else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("memcpy")) | ||
| diag(Call->getBeginLoc(), "do not use 'memcpy' to cast between pointers") | ||
| << Call->getSourceRange(); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::bugprone |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| //===--- BitwisePointerCastCheck.h - clang-tidy -----------------*- 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| /// Warns about code that tries to cast between pointers by means of | ||
| /// ``std::bit_cast`` or ``memcpy``. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/bitwise-pointer-cast.html | ||
| class BitwisePointerCastCheck : public ClangTidyCheck { | ||
| public: | ||
| BitwisePointerCastCheck(StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context) {} | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::bugprone | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| //===--- TaggedUnionMemberCountCheck.cpp - clang-tidy ---------------------===// | ||
| // | ||
| // 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 "TaggedUnionMemberCountCheck.h" | ||
| #include "../utils/OptionsUtils.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/ADT/SmallSet.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| static constexpr llvm::StringLiteral StrictModeOptionName = "StrictMode"; | ||
| static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName = | ||
| "EnableCountingEnumHeuristic"; | ||
| static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName = | ||
| "CountingEnumPrefixes"; | ||
| static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName = | ||
| "CountingEnumSuffixes"; | ||
|
|
||
| static constexpr bool StrictModeOptionDefaultValue = false; | ||
| static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true; | ||
| static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue = | ||
| ""; | ||
| static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue = | ||
| "count"; | ||
|
|
||
| static constexpr llvm::StringLiteral RootMatchBindName = "root"; | ||
| static constexpr llvm::StringLiteral UnionMatchBindName = "union"; | ||
| static constexpr llvm::StringLiteral TagMatchBindName = "tags"; | ||
|
|
||
| namespace { | ||
|
|
||
| AST_MATCHER_P2(RecordDecl, fieldCountOfKindIsOne, | ||
| ast_matchers::internal::Matcher<FieldDecl>, InnerMatcher, | ||
| StringRef, BindName) { | ||
| // BoundNodesTreeBuilder resets itself when a match occurs. | ||
| // So to avoid losing previously saved binds, a temporary instance | ||
| // is used for matching. | ||
| // | ||
| // For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559 | ||
| clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder; | ||
|
|
||
| const FieldDecl *FirstMatch = nullptr; | ||
| for (const FieldDecl *Field : Node.fields()) { | ||
| if (InnerMatcher.matches(*Field, Finder, &TempBuilder)) { | ||
| if (FirstMatch) { | ||
| return false; | ||
| } else { | ||
| FirstMatch = Field; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (FirstMatch) { | ||
| Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch)); | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck( | ||
| StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| StrictMode( | ||
| Options.get(StrictModeOptionName, StrictModeOptionDefaultValue)), | ||
| EnableCountingEnumHeuristic( | ||
| Options.get(EnableCountingEnumHeuristicOptionName, | ||
| EnableCountingEnumHeuristicOptionDefaultValue)), | ||
| CountingEnumPrefixes(utils::options::parseStringList( | ||
| Options.get(CountingEnumPrefixesOptionName, | ||
| CountingEnumPrefixesOptionDefaultValue))), | ||
| CountingEnumSuffixes(utils::options::parseStringList( | ||
| Options.get(CountingEnumSuffixesOptionName, | ||
| CountingEnumSuffixesOptionDefaultValue))) { | ||
| if (!EnableCountingEnumHeuristic) { | ||
| if (Options.get(CountingEnumPrefixesOptionName)) | ||
| configurationDiag("%0: Counting enum heuristic is disabled but " | ||
| "%1 is set") | ||
| << Name << CountingEnumPrefixesOptionName; | ||
| if (Options.get(CountingEnumSuffixesOptionName)) | ||
| configurationDiag("%0: Counting enum heuristic is disabled but " | ||
| "%1 is set") | ||
| << Name << CountingEnumSuffixesOptionName; | ||
| } | ||
| } | ||
|
|
||
| void TaggedUnionMemberCountCheck::storeOptions( | ||
| ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, StrictModeOptionName, StrictMode); | ||
| Options.store(Opts, EnableCountingEnumHeuristicOptionName, | ||
| EnableCountingEnumHeuristic); | ||
| Options.store(Opts, CountingEnumPrefixesOptionName, | ||
| utils::options::serializeStringList(CountingEnumPrefixes)); | ||
| Options.store(Opts, CountingEnumSuffixesOptionName, | ||
| utils::options::serializeStringList(CountingEnumSuffixes)); | ||
| } | ||
|
|
||
| void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) { | ||
|
|
||
| auto UnionField = fieldDecl(hasType(qualType( | ||
| hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion()))))))); | ||
|
|
||
| auto EnumField = fieldDecl(hasType( | ||
| qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl())))))); | ||
|
|
||
| auto hasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName); | ||
| auto hasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName); | ||
|
|
||
| Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), hasOneUnionField, | ||
| hasOneEnumField, unless(isImplicit())) | ||
| .bind(RootMatchBindName), | ||
| this); | ||
| } | ||
|
|
||
| bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const { | ||
| if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool { | ||
| return Name.starts_with_insensitive(Prefix); | ||
| })) | ||
| return true; | ||
| if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool { | ||
| return Name.ends_with_insensitive(Suffix); | ||
| })) | ||
| return true; | ||
| return false; | ||
| } | ||
|
|
||
| std::pair<const std::size_t, const EnumConstantDecl *> | ||
| TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) { | ||
| llvm::SmallSet<llvm::APSInt, 16> EnumValues; | ||
|
|
||
| const EnumConstantDecl *LastEnumConstant = nullptr; | ||
| for (const EnumConstantDecl *Enumerator : ED->enumerators()) { | ||
| EnumValues.insert(Enumerator->getInitVal()); | ||
| LastEnumConstant = Enumerator; | ||
| } | ||
|
|
||
| if (EnableCountingEnumHeuristic && LastEnumConstant && | ||
| isCountingEnumLikeName(LastEnumConstant->getName()) && | ||
| (LastEnumConstant->getInitVal() == (EnumValues.size() - 1))) { | ||
| return {EnumValues.size() - 1, LastEnumConstant}; | ||
| } | ||
|
|
||
| return {EnumValues.size(), nullptr}; | ||
| } | ||
|
|
||
| void TaggedUnionMemberCountCheck::check( | ||
| const MatchFinder::MatchResult &Result) { | ||
| const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName); | ||
| const auto *UnionField = | ||
| Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName); | ||
| const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName); | ||
|
|
||
| assert(Root && "Root is missing!"); | ||
| assert(UnionField && "UnionField is missing!"); | ||
| assert(TagField && "TagField is missing!"); | ||
| if (!Root || !UnionField || !TagField) | ||
| return; | ||
|
|
||
| const auto *UnionDef = | ||
| UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl(); | ||
| const auto *EnumDef = llvm::dyn_cast<EnumDecl>( | ||
| TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl()); | ||
|
|
||
| assert(UnionDef && "UnionDef is missing!"); | ||
| assert(EnumDef && "EnumDef is missing!"); | ||
| if (!UnionDef || !EnumDef) | ||
| return; | ||
|
|
||
| const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields()); | ||
| auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef); | ||
|
|
||
| if (UnionMemberCount > TagCount) { | ||
| diag(Root->getLocation(), | ||
| "tagged union has more data members (%0) than tags (%1)!") | ||
| << UnionMemberCount << TagCount; | ||
| } else if (StrictMode && UnionMemberCount < TagCount) { | ||
| diag(Root->getLocation(), | ||
| "tagged union has fewer data members (%0) than tags (%1)!") | ||
| << UnionMemberCount << TagCount; | ||
| } | ||
|
|
||
| if (CountingEnumConstantDecl) { | ||
| diag(CountingEnumConstantDecl->getLocation(), | ||
| "assuming that this constant is just an auxiliary value and not " | ||
| "used for indicating a valid union data member", | ||
| DiagnosticIDs::Note); | ||
| } | ||
| } | ||
|
|
||
| } // namespace clang::tidy::bugprone |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //===--- TaggedUnionMemberCountCheck.h - clang-tidy -------------*- 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| /// Gives warnings for tagged unions, where the number of tags is | ||
| /// different from the number of data members inside the union. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html | ||
| class TaggedUnionMemberCountCheck : public ClangTidyCheck { | ||
| public: | ||
| TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context); | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
|
|
||
| private: | ||
| const bool StrictMode; | ||
| const bool EnableCountingEnumHeuristic; | ||
| const std::vector<StringRef> CountingEnumPrefixes; | ||
| const std::vector<StringRef> CountingEnumSuffixes; | ||
|
|
||
| std::pair<const std::size_t, const EnumConstantDecl *> | ||
| getNumberOfEnumValues(const EnumDecl *ED); | ||
| bool isCountingEnumLikeName(StringRef Name) const; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::bugprone | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| add_clang_library(clangTidyPlugin STATIC | ||
| ClangTidyPlugin.cpp | ||
|
|
||
| LINK_LIBS | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| add_clang_library(clangdRemoteMarshalling STATIC | ||
| Marshalling.cpp | ||
|
|
||
| LINK_LIBS | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| //===--- SwapBinaryOperands.cpp ----------------------------------*- 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| #include "ParsedAST.h" | ||
| #include "Protocol.h" | ||
| #include "Selection.h" | ||
| #include "SourceCode.h" | ||
| #include "refactor/Tweak.h" | ||
| #include "support/Logger.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/AST/Expr.h" | ||
| #include "clang/AST/OperationKinds.h" | ||
| #include "clang/AST/Stmt.h" | ||
| #include "clang/Basic/LLVM.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "clang/Tooling/Core/Replacement.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/Support/Casting.h" | ||
| #include "llvm/Support/FormatVariadic.h" | ||
| #include <string> | ||
| #include <utility> | ||
|
|
||
| namespace clang { | ||
| namespace clangd { | ||
| namespace { | ||
| /// Check whether it makes logical sense to swap operands to an operator. | ||
| /// Assignment or member access operators are rarely swappable | ||
| /// while keeping the meaning intact, whereas comparison operators, mathematical | ||
| /// operators, etc. are often desired to be swappable for readability, avoiding | ||
| /// bugs by assigning to nullptr when comparison was desired, etc. | ||
| bool isOpSwappable(const BinaryOperatorKind Opcode) { | ||
| switch (Opcode) { | ||
| case BinaryOperatorKind::BO_Mul: | ||
| case BinaryOperatorKind::BO_Add: | ||
| case BinaryOperatorKind::BO_LT: | ||
| case BinaryOperatorKind::BO_GT: | ||
| case BinaryOperatorKind::BO_LE: | ||
| case BinaryOperatorKind::BO_GE: | ||
| case BinaryOperatorKind::BO_EQ: | ||
| case BinaryOperatorKind::BO_NE: | ||
| case BinaryOperatorKind::BO_And: | ||
| case BinaryOperatorKind::BO_Xor: | ||
| case BinaryOperatorKind::BO_Or: | ||
| case BinaryOperatorKind::BO_LAnd: | ||
| case BinaryOperatorKind::BO_LOr: | ||
| case BinaryOperatorKind::BO_Comma: | ||
| return true; | ||
| // Noncommutative operators: | ||
| case BinaryOperatorKind::BO_Div: | ||
| case BinaryOperatorKind::BO_Sub: | ||
| case BinaryOperatorKind::BO_Shl: | ||
| case BinaryOperatorKind::BO_Shr: | ||
| case BinaryOperatorKind::BO_Rem: | ||
| // <=> is noncommutative | ||
| case BinaryOperatorKind::BO_Cmp: | ||
| // Member access: | ||
| case BinaryOperatorKind::BO_PtrMemD: | ||
| case BinaryOperatorKind::BO_PtrMemI: | ||
| // Assignment: | ||
| case BinaryOperatorKind::BO_Assign: | ||
| case BinaryOperatorKind::BO_MulAssign: | ||
| case BinaryOperatorKind::BO_DivAssign: | ||
| case BinaryOperatorKind::BO_RemAssign: | ||
| case BinaryOperatorKind::BO_AddAssign: | ||
| case BinaryOperatorKind::BO_SubAssign: | ||
| case BinaryOperatorKind::BO_ShlAssign: | ||
| case BinaryOperatorKind::BO_ShrAssign: | ||
| case BinaryOperatorKind::BO_AndAssign: | ||
| case BinaryOperatorKind::BO_XorAssign: | ||
| case BinaryOperatorKind::BO_OrAssign: | ||
| return false; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /// Some operators are asymmetric and need to be flipped when swapping their | ||
| /// operands | ||
| /// @param[out] Opcode the opcode to potentially swap | ||
| /// If the opcode does not need to be swapped or is not swappable, does nothing | ||
| BinaryOperatorKind swapOperator(const BinaryOperatorKind Opcode) { | ||
| switch (Opcode) { | ||
| case BinaryOperatorKind::BO_LT: | ||
| return BinaryOperatorKind::BO_GT; | ||
|
|
||
| case BinaryOperatorKind::BO_GT: | ||
| return BinaryOperatorKind::BO_LT; | ||
|
|
||
| case BinaryOperatorKind::BO_LE: | ||
| return BinaryOperatorKind::BO_GE; | ||
|
|
||
| case BinaryOperatorKind::BO_GE: | ||
| return BinaryOperatorKind::BO_LE; | ||
|
|
||
| case BinaryOperatorKind::BO_Mul: | ||
| case BinaryOperatorKind::BO_Add: | ||
| case BinaryOperatorKind::BO_Cmp: | ||
| case BinaryOperatorKind::BO_EQ: | ||
| case BinaryOperatorKind::BO_NE: | ||
| case BinaryOperatorKind::BO_And: | ||
| case BinaryOperatorKind::BO_Xor: | ||
| case BinaryOperatorKind::BO_Or: | ||
| case BinaryOperatorKind::BO_LAnd: | ||
| case BinaryOperatorKind::BO_LOr: | ||
| case BinaryOperatorKind::BO_Comma: | ||
| case BinaryOperatorKind::BO_Div: | ||
| case BinaryOperatorKind::BO_Sub: | ||
| case BinaryOperatorKind::BO_Shl: | ||
| case BinaryOperatorKind::BO_Shr: | ||
| case BinaryOperatorKind::BO_Rem: | ||
| case BinaryOperatorKind::BO_PtrMemD: | ||
| case BinaryOperatorKind::BO_PtrMemI: | ||
| case BinaryOperatorKind::BO_Assign: | ||
| case BinaryOperatorKind::BO_MulAssign: | ||
| case BinaryOperatorKind::BO_DivAssign: | ||
| case BinaryOperatorKind::BO_RemAssign: | ||
| case BinaryOperatorKind::BO_AddAssign: | ||
| case BinaryOperatorKind::BO_SubAssign: | ||
| case BinaryOperatorKind::BO_ShlAssign: | ||
| case BinaryOperatorKind::BO_ShrAssign: | ||
| case BinaryOperatorKind::BO_AndAssign: | ||
| case BinaryOperatorKind::BO_XorAssign: | ||
| case BinaryOperatorKind::BO_OrAssign: | ||
| return Opcode; | ||
| } | ||
| llvm_unreachable("Unknown BinaryOperatorKind enum"); | ||
| } | ||
|
|
||
| /// Swaps the operands to a binary operator | ||
| /// Before: | ||
| /// x != nullptr | ||
| /// ^ ^^^^^^^ | ||
| /// After: | ||
| /// nullptr != x | ||
| class SwapBinaryOperands : public Tweak { | ||
| public: | ||
| const char *id() const final; | ||
|
|
||
| bool prepare(const Selection &Inputs) override; | ||
| Expected<Effect> apply(const Selection &Inputs) override; | ||
| std::string title() const override { | ||
| return llvm::formatv("Swap operands to {0}", | ||
| Op ? Op->getOpcodeStr() : "binary operator"); | ||
| } | ||
| llvm::StringLiteral kind() const override { | ||
| return CodeAction::REFACTOR_KIND; | ||
| } | ||
| bool hidden() const override { return false; } | ||
|
|
||
| private: | ||
| const BinaryOperator *Op; | ||
| }; | ||
|
|
||
| REGISTER_TWEAK(SwapBinaryOperands) | ||
|
|
||
| bool SwapBinaryOperands::prepare(const Selection &Inputs) { | ||
| for (const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor(); | ||
| N && !Op; N = N->Parent) { | ||
| // Stop once we hit a block, e.g. a lambda in one of the operands. | ||
| // This makes sure that the selection point is in the 'scope' of the binary | ||
| // operator, not from somewhere inside a lambda for example | ||
| // (5 < [](){ ^return 1; }) | ||
| if (llvm::isa_and_nonnull<CompoundStmt>(N->ASTNode.get<Stmt>())) | ||
| return false; | ||
| Op = dyn_cast_or_null<BinaryOperator>(N->ASTNode.get<Stmt>()); | ||
| // If we hit upon a nonswappable binary operator, ignore and keep going | ||
| if (Op && !isOpSwappable(Op->getOpcode())) { | ||
| Op = nullptr; | ||
| } | ||
| } | ||
| return Op != nullptr; | ||
| } | ||
|
|
||
| Expected<Tweak::Effect> SwapBinaryOperands::apply(const Selection &Inputs) { | ||
| const auto &Ctx = Inputs.AST->getASTContext(); | ||
| const auto &SrcMgr = Inputs.AST->getSourceManager(); | ||
|
|
||
| const auto LHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), | ||
| Op->getLHS()->getSourceRange()); | ||
| if (!LHSRng) | ||
| return error( | ||
| "Could not obtain range of the 'lhs' of the operator. Macros?"); | ||
| const auto RHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), | ||
| Op->getRHS()->getSourceRange()); | ||
| if (!RHSRng) | ||
| return error( | ||
| "Could not obtain range of the 'rhs' of the operator. Macros?"); | ||
| const auto OpRng = | ||
| toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), Op->getOperatorLoc()); | ||
| if (!OpRng) | ||
| return error("Could not obtain range of the operator itself. Macros?"); | ||
|
|
||
| const auto LHSCode = toSourceCode(SrcMgr, *LHSRng); | ||
| const auto RHSCode = toSourceCode(SrcMgr, *RHSRng); | ||
| const auto OperatorCode = toSourceCode(SrcMgr, *OpRng); | ||
|
|
||
| tooling::Replacements Result; | ||
| if (auto Err = Result.add(tooling::Replacement( | ||
| Ctx.getSourceManager(), LHSRng->getBegin(), LHSCode.size(), RHSCode))) | ||
| return std::move(Err); | ||
| if (auto Err = Result.add(tooling::Replacement( | ||
| Ctx.getSourceManager(), RHSRng->getBegin(), RHSCode.size(), LHSCode))) | ||
| return std::move(Err); | ||
| const auto SwappedOperator = swapOperator(Op->getOpcode()); | ||
| if (auto Err = Result.add(tooling::Replacement( | ||
| Ctx.getSourceManager(), OpRng->getBegin(), OperatorCode.size(), | ||
| Op->getOpcodeStr(SwappedOperator)))) | ||
| return std::move(Err); | ||
| return Effect::mainFileEdit(SrcMgr, std::move(Result)); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace clangd | ||
| } // namespace clang |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| //===-- SwapBinaryOperandsTests.cpp -----------------------------*- 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "TweakTesting.h" | ||
| #include "gmock/gmock-matchers.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace clang { | ||
| namespace clangd { | ||
| namespace { | ||
|
|
||
| TWEAK_TEST(SwapBinaryOperands); | ||
|
|
||
| TEST_F(SwapBinaryOperandsTest, Test) { | ||
| Context = Function; | ||
| EXPECT_EQ(apply("int *p = nullptr; bool c = ^p == nullptr;"), | ||
| "int *p = nullptr; bool c = nullptr == p;"); | ||
| EXPECT_EQ(apply("int *p = nullptr; bool c = p ^== nullptr;"), | ||
| "int *p = nullptr; bool c = nullptr == p;"); | ||
| EXPECT_EQ(apply("int x = 3; bool c = ^x >= 5;"), | ||
| "int x = 3; bool c = 5 <= x;"); | ||
| EXPECT_EQ(apply("int x = 3; bool c = x >^= 5;"), | ||
| "int x = 3; bool c = 5 <= x;"); | ||
| EXPECT_EQ(apply("int x = 3; bool c = x >=^ 5;"), | ||
| "int x = 3; bool c = 5 <= x;"); | ||
| EXPECT_EQ(apply("int x = 3; bool c = x >=^ 5;"), | ||
| "int x = 3; bool c = 5 <= x;"); | ||
| EXPECT_EQ(apply("int f(); int x = 3; bool c = x >=^ f();"), | ||
| "int f(); int x = 3; bool c = f() <= x;"); | ||
| EXPECT_EQ(apply(R"cpp( | ||
| int f(); | ||
| #define F f | ||
| int x = 3; bool c = x >=^ F(); | ||
| )cpp"), | ||
| R"cpp( | ||
| int f(); | ||
| #define F f | ||
| int x = 3; bool c = F() <= x; | ||
| )cpp"); | ||
| EXPECT_EQ(apply(R"cpp( | ||
| int f(); | ||
| #define F f() | ||
| int x = 3; bool c = x >=^ F; | ||
| )cpp"), | ||
| R"cpp( | ||
| int f(); | ||
| #define F f() | ||
| int x = 3; bool c = F <= x; | ||
| )cpp"); | ||
| EXPECT_EQ(apply(R"cpp( | ||
| int f(bool); | ||
| #define F(v) f(v) | ||
| int x = 0; | ||
| bool c = F(x^ < 5); | ||
| )cpp"), | ||
| R"cpp( | ||
| int f(bool); | ||
| #define F(v) f(v) | ||
| int x = 0; | ||
| bool c = F(5 > x); | ||
| )cpp"); | ||
| ExtraArgs = {"-std=c++20"}; | ||
| Context = CodeContext::File; | ||
| EXPECT_UNAVAILABLE(R"cpp( | ||
| namespace std { | ||
| struct strong_ordering { | ||
| int val; | ||
| static const strong_ordering less; | ||
| static const strong_ordering equivalent; | ||
| static const strong_ordering equal; | ||
| static const strong_ordering greater; | ||
| }; | ||
| inline constexpr strong_ordering strong_ordering::less {-1}; | ||
| inline constexpr strong_ordering strong_ordering::equivalent {0}; | ||
| inline constexpr strong_ordering strong_ordering::equal {0}; | ||
| inline constexpr strong_ordering strong_ordering::greater {1}; | ||
| }; | ||
| #define F(v) v | ||
| int x = 0; | ||
| auto c = F(5^ <=> x); | ||
| )cpp"); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace clangd | ||
| } // namespace clang |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| .. title:: clang-tidy - bugprone-bitwise-pointer-cast | ||
|
|
||
| bugprone-bitwise-pointer-cast | ||
| ============================= | ||
|
|
||
| Warns about code that tries to cast between pointers by means of | ||
| ``std::bit_cast`` or ``memcpy``. | ||
|
|
||
| The motivation is that ``std::bit_cast`` is advertised as the safe alternative | ||
| to type punning via ``reinterpret_cast`` in modern C++. However, one should not | ||
| blindly replace ``reinterpret_cast`` with ``std::bit_cast``, as follows: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| int x{}; | ||
| -float y = *reinterpret_cast<float*>(&x); | ||
| +float y = *std::bit_cast<float*>(&x); | ||
|
|
||
| The drop-in replacement behaves exactly the same as ``reinterpret_cast``, and | ||
| Undefined Behavior is still invoked. ``std::bit_cast`` is copying the bytes of | ||
| the input pointer, not the pointee, into an output pointer of a different type, | ||
| which may violate the strict aliasing rules. However, simply looking at the | ||
| code, it looks "safe", because it uses ``std::bit_cast`` which is advertised as | ||
| safe. | ||
|
|
||
| The solution to safe type punning is to apply ``std::bit_cast`` on value types, | ||
| not on pointer types: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| int x{}; | ||
| float y = std::bit_cast<float>(x); | ||
|
|
||
| This way, the bytes of the input object are copied into the output object, which | ||
| is much safer. Do note that Undefined Behavior can still occur, if there is no | ||
| value of type ``To`` corresponding to the value representation produced. | ||
| Compilers may be able to optimize this copy and generate identical assembly to | ||
| the original ``reinterpret_cast`` version. | ||
|
|
||
| Code before C++20 may backport ``std::bit_cast`` by means of ``memcpy``, or | ||
| simply call ``memcpy`` directly, which is equally problematic. This is also | ||
| detected by this check: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| int* x{}; | ||
| float* y{}; | ||
| std::memcpy(&y, &x, sizeof(x)); | ||
|
|
||
| Alternatively, if a cast between pointers is truly wanted, ``reinterpret_cast`` | ||
| should be used, to clearly convey the intent and enable warnings from compilers | ||
| and linters, which should be addressed accordingly. |