| 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // RUN: %check_clang_tidy -std=c++20 %s bugprone-bitwise-pointer-cast %t | ||
|
|
||
| void memcpy(void* to, void* dst, unsigned long long size) | ||
| { | ||
| // Dummy implementation for the purpose of the test | ||
| } | ||
|
|
||
| namespace std | ||
| { | ||
| template <typename To, typename From> | ||
| To bit_cast(From from) | ||
| { | ||
| // Dummy implementation for the purpose of the test | ||
| To to{}; | ||
| return to; | ||
| } | ||
|
|
||
| using ::memcpy; | ||
| } | ||
|
|
||
| void pointer2pointer() | ||
| { | ||
| int x{}; | ||
| float bad = *std::bit_cast<float*>(&x); // UB, but looks safe due to std::bit_cast | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast] | ||
| float good = std::bit_cast<float>(x); // Well-defined | ||
|
|
||
| using IntPtr = int*; | ||
| using FloatPtr = float*; | ||
| IntPtr x2{}; | ||
| float bad2 = *std::bit_cast<FloatPtr>(x2); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast] | ||
| } | ||
|
|
||
| void pointer2pointer_memcpy() | ||
| { | ||
| int x{}; | ||
| int* px{}; | ||
| float y{}; | ||
| float* py{}; | ||
|
|
||
| memcpy(&py, &px, sizeof(px)); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast] | ||
| std::memcpy(&py, &px, sizeof(px)); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast] | ||
|
|
||
| std::memcpy(&y, &x, sizeof(x)); | ||
| } | ||
|
|
||
| // Pointer-integer conversions are allowed by this check | ||
| void int2pointer() | ||
| { | ||
| unsigned long long addr{}; | ||
| float* p = std::bit_cast<float*>(addr); | ||
| std::memcpy(&p, &addr, sizeof(addr)); | ||
| } | ||
|
|
||
| void pointer2int() | ||
| { | ||
| float* p{}; | ||
| auto addr = std::bit_cast<unsigned long long>(p); | ||
| std::memcpy(&addr, &p, sizeof(p)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // RUN: %check_clang_tidy %s bugprone-bitwise-pointer-cast %t | ||
|
|
||
| void memcpy(void* to, void* dst, unsigned long long size) | ||
| { | ||
| // Dummy implementation for the purpose of the test | ||
| } | ||
|
|
||
| namespace std | ||
| { | ||
| using ::memcpy; | ||
| } | ||
|
|
||
| void pointer2pointer() | ||
| { | ||
| int x{}; | ||
| int* px{}; | ||
| float y{}; | ||
| float* py{}; | ||
|
|
||
| memcpy(&py, &px, sizeof(px)); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast] | ||
| std::memcpy(&py, &px, sizeof(px)); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast] | ||
|
|
||
| std::memcpy(&y, &x, sizeof(x)); | ||
| } | ||
|
|
||
| // Pointer-integer conversions are allowed by this check | ||
| void int2pointer() | ||
| { | ||
| unsigned long long addr{}; | ||
| float* p{}; | ||
| std::memcpy(&p, &addr, sizeof(addr)); | ||
| } | ||
|
|
||
| void pointer2int() | ||
| { | ||
| unsigned long long addr{}; | ||
| float* p{}; | ||
| std::memcpy(&addr, &p, sizeof(p)); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # This file is for the llvm+clang options that are specific to building | ||
| # a cross-toolchain targeting hexagon linux. | ||
| set(DEFAULT_SYSROOT "../target/hexagon-unknown-linux-musl/" CACHE STRING "") | ||
| set(CLANG_LINKS_TO_CREATE | ||
| hexagon-linux-musl-clang++ | ||
| hexagon-linux-musl-clang | ||
| hexagon-unknown-linux-musl-clang++ | ||
| hexagon-unknown-linux-musl-clang | ||
| hexagon-none-elf-clang++ | ||
| hexagon-none-elf-clang | ||
| hexagon-unknown-none-elf-clang++ | ||
| hexagon-unknown-none-elf-clang | ||
| CACHE STRING "") | ||
|
|
||
| set(LLVM_INSTALL_TOOLCHAIN_ONLY ON CACHE BOOL "") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
|
|
||
| set(LLVM_TARGETS_TO_BUILD "Hexagon" CACHE STRING "") | ||
| set(LLVM_DEFAULT_TARGET_TRIPLE "hexagon-unknown-linux-musl" CACHE STRING "") | ||
| set(CLANG_DEFAULT_CXX_STDLIB "libc++" CACHE STRING "") | ||
| set(CLANG_DEFAULT_OBJCOPY "llvm-objcopy" CACHE STRING "") | ||
| set(CLANG_DEFAULT_RTLIB "compiler-rt" CACHE STRING "") | ||
| set(CLANG_DEFAULT_UNWINDLIB "libunwind" CACHE STRING "") | ||
| set(CLANG_DEFAULT_LINKER "lld" CACHE STRING "") | ||
| set(LLVM_ENABLE_PROJECTS "clang;lld" CACHE STRING "") | ||
|
|
||
| set(LLVM_INCLUDE_TESTS OFF CACHE BOOL "") | ||
| set(LLVM_INCLUDE_DOCS OFF CACHE BOOL "") | ||
| # Enabling toolchain-only causes problems when doing some of the | ||
| # subsequent builds, will need to investigate: | ||
| set(LLVM_INSTALL_TOOLCHAIN_ONLY OFF CACHE BOOL "") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| .. include:: ../Maintainers.rst |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -104,7 +104,7 @@ Design Documents | |
| .. toctree:: | ||
| :maxdepth: 1 | ||
|
|
||
| Maintainers | ||
| InternalsManual | ||
| DriverInternals | ||
| Multilib | ||
|
|
||