diff --git a/bolt/README.md b/bolt/README.md index 55f742c5019f..2e35cf8a8a98 100644 --- a/bolt/README.md +++ b/bolt/README.md @@ -202,6 +202,26 @@ $ merge-fdata *.fdata > combined.fdata Use `combined.fdata` for **Step 3** above to generate a universally optimized binary. +## Identifying a Binary Modified By BOLT + +A binary that has been modified by BOLT will include a `bolt_info` note and may +have extra sections with `bolt` in their name. + +You can use `readelf` to find these: +``` +$ readelf -S | grep bolt + [11] .bolt.org.eh_frame PROGBITS <...> +<...> + [39] .note.bolt_info NOTE <...> +``` +The note can be displayed with: +``` +$ readelf -p .note.bolt_info +String dump of section '.note.bolt_info': + <...> + [ 10] BOLT revision: <...> +``` + ## License BOLT is licensed under the [Apache License v2.0 with LLVM Exceptions](./LICENSE.TXT). diff --git a/bolt/docs/BAT.md b/bolt/docs/BAT.md index 817ad288aa34..fa43e81553d5 100644 --- a/bolt/docs/BAT.md +++ b/bolt/docs/BAT.md @@ -61,6 +61,7 @@ Functions table: ### Functions table Hot and cold functions tables share the encoding except differences marked below. + Header: | Entry | Encoding | Description | | ------ | ----- | ----------- | diff --git a/bolt/docs/CommandLineArgumentReference.md b/bolt/docs/CommandLineArgumentReference.md index 7c6e01d669b7..0dbf6f59d5e8 100644 --- a/bolt/docs/CommandLineArgumentReference.md +++ b/bolt/docs/CommandLineArgumentReference.md @@ -811,6 +811,15 @@ Specify file name of the runtime instrumentation library +- `--runtime-lib-init-hook=` + + Primary target for hooking runtime library initialization, used in + fallback order of availability in input binary (entry_point -> init + -> init_array) (default: entry_point) + - `entry_point`: use ELF Header Entry Point + - `init`: use ELF DT_INIT entry + - `init_array`: use ELF .init_array entry + - `--sctc-mode=` Mode for simplify conditional tail calls diff --git a/bolt/docs/PacRetDesign.md b/bolt/docs/PacRetDesign.md deleted file mode 100644 index f3fe5fbd522c..000000000000 --- a/bolt/docs/PacRetDesign.md +++ /dev/null @@ -1,228 +0,0 @@ -# Optimizing binaries with pac-ret hardening - -This is a design document about processing the `DW_CFA_AARCH64_negate_ra_state` -DWARF instruction in BOLT. As it describes internal design decisions, the -intended audience is BOLT developers. The document is an updated version of the -[RFC posted on the LLVM Discourse](https://discourse.llvm.org/t/rfc-bolt-aarch64-handle-opnegaterastate-to-enable-optimizing-binaries-with-pac-ret-hardening/86594). - - -`DW_CFA_AARCH64_negate_ra_state` is also referred to as `.cfi_negate_ra_state` -in assembly, or `OpNegateRAState` in BOLT sources. In this document, I will use -**negate-ra-state** as a shorthand. - -## Introduction - -### Pointer Authentication - -For more information, see the [pac-ret section of the BOLT-binary-analysis document](BinaryAnalysis.md#pac-ret-analysis). - -### DW_CFA_AARCH64_negate_ra_state - -The negate-ra-state CFI is a vendor-specific Call Frame Instruction defined in -the [Arm ABI](https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#id1). - -``` -The DW_CFA_AARCH64_negate_ra_state operation negates bit[0] of the RA_SIGN_STATE pseudo-register. -``` - -This bit indicates to the unwinder whether the current return address is signed -or not (hence the name). The unwinder uses this information to authenticate the -pointer, and remove the Pointer Authentication Code (PAC) bits. -Incorrect placement of negate-ra-state CFIs causes the unwinder to either attempt -to authenticate an unsigned pointer (resulting in a segmentation fault), or skip -authentication on a signed pointer, which can also cause a fault. - -Note: some unwinders use the `xpac` instruction to strip the PAC bits without -authenticating the pointer. This is an incorrect (incomplete) implementation, -as it allows control-flow modification in the case of unwinding. - -There are no DWARF instructions to directly set or clear the RA State. However, -two other CFIs can also affect the RA state: -- `DW_CFA_remember_state`: this CFI stores register rules onto an implicit stack. -- `DW_CFA_restore_state`: this CFI pops rules from this stack. - -Example: - -| CFI | Effect on RA state | -| ------------------------------ | ------------------------------ | -| (default) | 0 | -| DW_CFA_AARCH64_negate_ra_state | 0 -> 1 | -| DW_CFA_remember_state | 1 pushed to the stack | -| DW_CFA_AARCH64_negate_ra_state | 1 -> 0 | -| DW_CFA_restore_state | 0 -> 1 (popped from the stack) | - -The Arm ABI also defines the DW_CFA_AARCH64_negate_ra_state_with_pc CFI, but it -is not widely used, and is [likely to become deprecated](https://github.com/ARM-software/abi-aa/issues/327). - -### Where are these CFIs needed? - -Whenever two consecutive instructions have different RA states, the unwinder must -be informed of the change. This typically occurs during pointer signing or -authentication. If adjacent instructions differ in RA state but neither signs -nor authenticates the return address, they must belong to different control flow -paths. One is part of an execution path with signed RA, the other is part of a -path with an unsigned RA. - -In the example below, the first BasicBlock ends in a conditional branch, and -jumps to two different BasicBlocks, each with their own authentication, and -return. The instructions on the border of the second and third BasicBlock have -different RA states. The `ret` at the end of the second BasicBlock is in unsigned -state. The start of the third BasicBlock is after the `paciasp` in the control -flow, but before the authentication. In this case, a negate-ra-state is needed -at the end of the second BasicBlock. - -``` - +----------------+ - | paciasp | - | | - | b.cc | - +--------+-------+ - | -+----------------+ -| | -| +--------v-------+ -| | | -| | autiasp | -| | ret | // RA: unsigned -| +----------------+ -+----------------+ - | - +--------v-------+ // RA: signed - | | - | autiasp | - | ret | - +----------------+ -``` - -> [!important] -> The unwinder does not follow the control flow graph. It reads unwind -> information in the layout order. - -Because these locations are dependent on how the function layout looks, -negate-ra-state CFIs will become invalid during BasicBlock reordering. - -## Solution design - -The implementation introduces two new passes: -1. `MarkRAStatesPass`: assigns the RA state to each instruction based on the CFIs - in the input binary -2. `InsertNegateRAStatePass`: reads those assigned instruction RA states after - optimizations, and emits `DW_CFA_AARCH64_negate_ra_state` CFIs at the correct - places: wherever there is a state change between two consecutive instructions - in the layout order. - -To track metadata on individual instructions, the `MCAnnotation` class was -extended. These also have helper functions in `MCPlusBuilder`. - -### Saving annotations at CFI reading - -CFIs are read and added to BinaryFunctions in `CFIReaderWriter::FillCFIInfoFor`. -At this point, we add MCAnnotations about negate-ra-state, remember-state and -restore-state CFIs to the instructions they refer to. This is to not interfere -with the CFI processing that already happens in BOLT (e.g. remember-state and -restore-state CFIs are removed in `normalizeCFIState` for reasons unrelated to PAC). - -As we add the MCAnnotations *to instructions*, we have to account for the case -where the function starts with a CFI altering the RA state. As CFIs modify the RA -state of the instructions before them, we cannot add the annotation to the first -instruction. -This special case is handled by adding an `initialRAState` bool to each BinaryFunction. -If the `Offset` the CFI refers to is zero, we don't store an annotation, but set -the `initialRAState` in `FillCFIInfoFor`. This information is then used in -`MarkRAStates`. - -### Binaries without DWARF info - -In some cases, the DWARF tables are stripped from the binary. These programs -usually have some other unwind-mechanism. -These passes only run on functions that include at least one negate-ra-state CFI. -This avoids processing functions that do not use Pointer Authentication, or on -functions that use Pointer Authentication, but do not have DWARF info. - -In summary: -- pointer auth is not used: no change, the new passes do not run. -- pointer auth is used, but DWARF info is stripped: no change, the new passes - do not run. -- pointer auth is used, and we have DWARF CFIs: passes run, and rewrite the - negate-ra-state CFI. - -### MarkRAStates pass - -This pass runs before optimizations reorder anything. - -It processes MCAnnotations generated during the CFI reading stage to check if -instructions have either of the three CFIs that can modify RA state: -- negate-ra-state, -- remember-state, -- restore-state. - -Then it adds new MCAnnotations to each instruction, indicating their RA state. -Those annotations are: -- Signed, -- Unsigned. - -Below is a simple example, that shows the two different type of annotations: -what we have before the pass, and after it. - -| Instruction | Before | After | -| ----------------------------- | --------------- | -------- | -| paciasp | negate-ra-state | unsigned | -| stp x29, x30, [sp, #-0x10]! | | signed | -| mov x29, sp | | signed | -| ldp x29, x30, [sp], #0x10 | | signed | -| autiasp | negate-ra-state | signed | -| ret | | unsigned | - -##### Error handling in MarkRAState Pass: - -Whenever the MarkRAStates pass finds inconsistencies in the current -BinaryFunction, it marks the function as ignored using `BF.setIgnored()`. BOLT -will not optimize this function but will emit it unchanged in the original section -(`.bolt.org.text`). - -The inconsistencies are as follows: -- finding a `pac*` instruction when already in signed state -- finding an `aut*` instruction when already in unsigned state -- finding `pac*` and `aut*` instructions without `.cfi_negate_ra_state`. - -Users will be informed about the number of ignored functions in the pass, the -exact functions ignored, and the found inconsistency. - -### InsertNegateRAStatePass - -This pass runs after optimizations. It performns the _inverse_ of MarkRAState pa s: -1. it reads the RA state annotations attached to the instructions, and -2. whenever the state changes, it adds a PseudoInstruction that holds an - OpNegateRAState CFI. - -##### Covering newly generated instructions: - -Some BOLT passes can add new Instructions. In InsertNegateRAStatePass, we have -to know what RA state these have. - -The current solution has the `inferUnknownStates` function to cover these, using -a fairly simple strategy: unknown states inherit the last known state. - -This will be updated to a more robust solution. - -> [!important] -> As issue #160989 describes, unwind info is incorrect in stubs with multiple callers. -> For this same reason, we cannot generate correct pac-specific unwind info: the signess -> of the _incorrect_ return address is meaningless. - -### Optimizations requiring special attention - -Marking states before optimizations ensure that instructions can be moved around -freely. The only special case is function splitting. When a function is split, -the split part becomes a new function in the emitted binary. For unwinding to -work, it needs to "replay" all CFIs that lead up to the split point. BOLT does -this for other CFIs. As negate-ra-state is not read (only stored as an Annotation), -we have to do this manually in InsertNegateRAStatePass. Here, if the split part -starts with an instruction that has Signed RA state, we add a negate-ra-state CFI -to indicate this. - -## Option to disallow the feature - -The feature can be guarded with the `--update-branch-prediction` flag, which is -on by default. If the flag is set to false, and a function -`containedNegateRAState()` after `FillCFIInfoFor()`, BOLT exits with an error. diff --git a/bolt/docs/PointerAuthDesign.md b/bolt/docs/PointerAuthDesign.md new file mode 100644 index 000000000000..d10179504b7f --- /dev/null +++ b/bolt/docs/PointerAuthDesign.md @@ -0,0 +1,240 @@ +# Optimizing binaries with pac-ret hardening + +This is a design document about processing the `DW_CFA_AARCH64_negate_ra_state` +DWARF instruction in BOLT. As it describes internal design decisions, the +intended audience is BOLT developers. The document is an updated version of the +[RFC posted on the LLVM Discourse](https://discourse.llvm.org/t/rfc-bolt-aarch64-handle-opnegaterastate-to-enable-optimizing-binaries-with-pac-ret-hardening/86594). + + +`DW_CFA_AARCH64_negate_ra_state` is also referred to as `.cfi_negate_ra_state` +in assembly, or `OpNegateRAState` in BOLT sources. In this document, I will use +**negate-ra-state** as a shorthand. + +Note: there are two resolutions for CFI: +- Call Frame Instruction: individual DWARF instruction, e.g. negate-ra-state +- Control Flow Integrity: a security mechanism, e.g. pointer authentication. + +## Introduction + +### Pointer Authentication + +For more information, see the [pac-ret section of the BOLT-binary-analysis document](BinaryAnalysis.md#pac-ret-analysis). + +### DW_CFA_AARCH64_negate_ra_state + +The negate-ra-state CFI is a vendor-specific Call Frame Instruction defined in +the [Arm ABI](https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#id1). + +``` +The DW_CFA_AARCH64_negate_ra_state operation negates bit[0] of the RA_SIGN_STATE pseudo-register. +``` + +This bit indicates to the unwinder whether the current return address is signed +or not (hence the name). The unwinder uses this information to authenticate the +pointer, and remove the Pointer Authentication Code (PAC) bits. +Incorrect placement of negate-ra-state CFIs causes the unwinder to either attempt +to authenticate an unsigned pointer (resulting in a segmentation fault), or skip +authentication on a signed pointer, which can also cause a fault. + +Note: some unwinders use the `xpac` instruction to strip the PAC bits without +authenticating the pointer. This is an incorrect (incomplete) implementation, +as it allows control-flow modification in the case of unwinding. + +There are no DWARF instructions to directly set or clear the RA State. However, +two other CFIs can also affect the RA state: +- `DW_CFA_remember_state`: this CFI stores register rules onto an implicit stack. +- `DW_CFA_restore_state`: this CFI pops rules from this stack. + +Example: + +| CFI | Effect on RA state | +| ------------------------------ | ------------------------------ | +| (default) | 0 | +| DW_CFA_AARCH64_negate_ra_state | 0 -> 1 | +| DW_CFA_remember_state | 1 pushed to the stack | +| DW_CFA_AARCH64_negate_ra_state | 1 -> 0 | +| DW_CFA_restore_state | 0 -> 1 (popped from the stack) | + +The Arm ABI also defines the DW_CFA_AARCH64_negate_ra_state_with_pc CFI, but it +is not widely used, and is [likely to become deprecated](https://github.com/ARM-software/abi-aa/issues/327). + +### Where are these CFIs needed? + +Whenever two consecutive instructions have different RA states, the unwinder must +be informed of the change. This typically occurs during pointer signing or +authentication. If adjacent instructions differ in RA state but neither signs +nor authenticates the return address, they must belong to different control flow +paths. One is part of an execution path with signed RA, the other is part of a +path with an unsigned RA. + +In the example below, the first BasicBlock ends in a conditional branch, and +jumps to two different BasicBlocks, each with their own authentication, and +return. The instructions on the border of the second and third BasicBlock have +different RA states. The `ret` at the end of the second BasicBlock is in unsigned +state. The start of the third BasicBlock is after the `paciasp` in the control +flow, but before the authentication. In this case, a negate-ra-state is needed +at the end of the second BasicBlock. + +``` + +----------------+ + | paciasp | + | | + | b.cc | + +--------+-------+ + | ++----------------+ +| | +| +--------v-------+ +| | | +| | autiasp | +| | ret | // RA: unsigned +| +----------------+ ++----------------+ + | + +--------v-------+ // RA: signed + | | + | autiasp | + | ret | + +----------------+ +``` + +> [!important] +> The unwinder does not follow the control flow graph. It reads unwind +> information in the layout order. + +Because these locations are dependent on how the function layout looks, +negate-ra-state CFIs will become invalid during BasicBlock reordering. + +## Solution design + +The implementation introduces two new passes: +1. `PointerAuthCFIAnalyzer`: assigns the RA state to each instruction based on + the CFIs in the input binary +2. `PointerAuthCFIFixup`: reads those assigned instruction RA states after + optimizations, and emits `DW_CFA_AARCH64_negate_ra_state` CFIs at the correct + places: wherever there is a state change between two consecutive instructions + in the layout order. + +To track metadata on individual instructions, the `MCAnnotation` class was +extended. These also have helper functions in `MCPlusBuilder`. + +### Saving annotations at CFI reading + +CFIs are read and added to BinaryFunctions in `CFIReaderWriter::FillCFIInfoFor`. +At this point, we add MCAnnotations about negate-ra-state, remember-state and +restore-state CFIs to the instructions they refer to. This is to not interfere +with the CFI processing that already happens in BOLT (e.g. remember-state and +restore-state CFIs are removed in `normalizeCFIState` for reasons unrelated to PAC). + +As we add the MCAnnotations *to instructions*, we have to account for the case +where the function starts with a CFI altering the RA state. As CFIs modify the RA +state of the instructions before them, we cannot add the annotation to the first +instruction. +This special case is handled by adding an `initialRAState` bool to each BinaryFunction. +If the `Offset` the CFI refers to is zero, we don't store an annotation, but set +the `initialRAState` in `FillCFIInfoFor`. This information is then used in +`PointerAuthCFIAnalyzer`. + +### Binaries without DWARF info + +In some cases, the DWARF tables are stripped from the binary. These programs +usually have some other unwind-mechanism. +These passes only run on functions that include at least one negate-ra-state CFI. +This avoids processing functions that do not use Pointer Authentication, or on +functions that use Pointer Authentication, but do not have DWARF info. + +In summary: +- pointer auth is not used: no change, the new passes do not run. +- pointer auth is used, but DWARF info is stripped: no change, the new passes + do not run. +- pointer auth is used, and we have DWARF CFIs: passes run, and rewrite the + negate-ra-state CFI. + +### PointerAuthCFIAnalyzer pass + +This pass runs before optimizations reorder anything. + +It processes MCAnnotations generated during the CFI reading stage to check if +instructions have either of the three CFIs that can modify RA state: +- negate-ra-state, +- remember-state, +- restore-state. + +Then it adds new MCAnnotations to each instruction, indicating their RA state. +Those annotations are: +- Signed, +- Unsigned. + +Below is a simple example, that shows the two different type of annotations: +what we have before the pass, and after it. + +| Instruction | Before | After | +| ----------------------------- | --------------- | -------- | +| paciasp | negate-ra-state | unsigned | +| stp x29, x30, [sp, #-0x10]! | | signed | +| mov x29, sp | | signed | +| ldp x29, x30, [sp], #0x10 | | signed | +| autiasp | negate-ra-state | signed | +| ret | | unsigned | + +##### Error handling in PointerAuthCFIAnalyzer pass: + +Whenever the PointerAuthCFIAnalyzer pass finds inconsistencies in the current +BinaryFunction, it marks the function as ignored using `BF.setIgnored()`. BOLT +will not optimize this function but will emit it unchanged in the original section +(`.bolt.org.text`). + +The inconsistencies are as follows: +- finding a `pac*` instruction when already in signed state +- finding an `aut*` instruction when already in unsigned state +- finding `pac*` and `aut*` instructions without `.cfi_negate_ra_state`. + +Users will be informed about the number of ignored functions in the pass, the +exact functions ignored, and the found inconsistency. + +### PointerAuthCFIFixup + +This pass runs after optimizations. It performs the _inverse_ of PointerAuthCFIAnalyzer +pass: +1. it reads the RA state annotations attached to the instructions, and +2. whenever the state changes, it adds a PseudoInstruction that holds an + OpNegateRAState CFI. + +##### Covering newly generated instructions: + +Some BOLT passes can add new Instructions. In PointerAuthCFIFixup, we have +to know what RA state these have. + +> [!important] +> As issue #160989 explains, unwind info is missing from stubs. +> For this same reason, we cannot generate correct pac-specific unwind info: the +> signedness of the _incorrect_ return address is meaningless. + +Assignment of RAStates to newly generated instructions is done in `inferUnknownStates`. +We have two different cases to cover: + +1. If a BasicBlock has some instructions with known RA state, and some without, we + can copy the RAState of known instructions to the unknown ones. As the control + flow only changes between BasicBlocks, instructions in the same BasicBlock have + the same return address. (The exception is noreturn calls, but these would only + cause problems, if the newly inserted instruction is right after the call.) + +2. If a BasicBlock has no instructions with known RAState, we have to copy the + RAState of the previous BasicBlock in layout order. + +### Optimizations requiring special attention + +Marking states before optimizations ensure that instructions can be moved around +freely. The only special case is function splitting. When a function is split, +the split part becomes a new function in the emitted binary. For unwinding to +work, it needs to "replay" all CFIs that lead up to the split point. BOLT does +this for other CFIs. As negate-ra-state is not read (only stored as an Annotation), +we have to do this manually in PointerAuthCFIFixup. Here, if the split part +starts with an instruction that has Signed RA state, we add a negate-ra-state CFI +to indicate this. + +## Option to disallow the feature + +The feature can be guarded with the `--update-branch-prediction` flag, which is +on by default. If the flag is set to false, and a function +`containedNegateRAState()` after `FillCFIInfoFor()`, BOLT exits with an error. diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h index 2af1d330b754..aefb40bfc603 100644 --- a/bolt/include/bolt/Core/BinaryContext.h +++ b/bolt/include/bolt/Core/BinaryContext.h @@ -807,6 +807,15 @@ class BinaryContext { /// the execution of the binary is completed. std::optional FiniFunctionAddress; + /// DT_INIT. + std::optional InitAddress; + + /// DT_INIT_ARRAY. Only used when DT_INIT is not set. + std::optional InitArrayAddress; + + /// DT_INIT_ARRAYSZ. Only used when DT_INIT is not set. + std::optional InitArraySize; + /// DT_FINI. std::optional FiniAddress; @@ -934,10 +943,11 @@ class BinaryContext { /// that should be used by the branch. For example, main or secondary entry /// point. /// - /// If \p Address is an invalid destination, such as a constant island, return - /// nullptr and mark \p BF as ignored, since we cannot properly handle a - /// branch to a constant island. - MCSymbol *handleExternalBranchTarget(uint64_t Address, BinaryFunction &BF); + /// This function also performs validations: If \p Address points to an + /// invalid instruction or lies within a constant island, return nullptr and + /// mark both \p Source and \p Target as ignored. + MCSymbol *handleExternalBranchTarget(uint64_t Address, BinaryFunction &Source, + BinaryFunction &Target); /// Analyze memory contents at the given \p Address and return the type of /// memory contents (such as a possible jump table). @@ -1104,7 +1114,7 @@ class BinaryContext { return FragmentClasses.isEquivalent(LHS, RHS); } - /// Add interprocedural reference for \p Function to \p Address + /// Add interprocedural branch reference from \p Function to \p Address. void addInterproceduralReference(BinaryFunction *Function, uint64_t Address) { InterproceduralReferences.push_back({Function, Address}); } @@ -1119,7 +1129,8 @@ class BinaryContext { /// argument is false. bool handleAArch64Veneer(uint64_t Address, bool MatchOnly = false); - /// Resolve inter-procedural dependencies from + /// Resolve inter-procedural branch dependencies discovered during + /// disassembly. void processInterproceduralReferences(); /// Skip functions with all parent and child fragments transitively. diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h index a720c6af216d..12aef415aa05 100644 --- a/bolt/include/bolt/Core/BinaryFunction.h +++ b/bolt/include/bolt/Core/BinaryFunction.h @@ -385,6 +385,10 @@ class BinaryFunction { /// True if the function should not have an associated symbol table entry. bool IsAnonymous{false}; + /// Indicates whether branch validation has already been performed, + /// to avoid redundant processing. + bool NeedBranchValidation{true}; + /// Name for the section this function code should reside in. std::string CodeSectionName; @@ -731,11 +735,12 @@ class BinaryFunction { Symbols.push_back(BC.Ctx->getOrCreateSymbol(Name)); } - /// This constructor is used to create an injected function + /// This constructor is used to create an injected function, i.e. a function + /// that didn't originate in the input file. BinaryFunction(const std::string &Name, BinaryContext &BC, bool IsSimple) : Address(0), Size(0), BC(BC), IsSimple(IsSimple), - CodeSectionName(buildCodeSectionName(Name, BC)), - ColdCodeSectionName(buildColdCodeSectionName(Name, BC)), + CodeSectionName(BC.getInjectedCodeSectionName()), + ColdCodeSectionName(BC.getInjectedColdCodeSectionName()), FunctionNumber(++Count) { Symbols.push_back(BC.Ctx->getOrCreateSymbol(Name)); IsInjected = true; @@ -2320,6 +2325,11 @@ class BinaryFunction { /// zero-value bytes. bool isZeroPaddingAt(uint64_t Offset) const; + /// Validate if the target of any internal direct branch/call is a valid + /// executable instruction. + /// Return true if all the targets are valid, false otherwise. + bool validateInternalBranches(); + /// Check that entry points have an associated instruction at their /// offsets after disassembly. void postProcessEntryPoints(); @@ -2356,10 +2366,9 @@ class BinaryFunction { bool postProcessIndirectBranches(MCPlusBuilder::AllocatorIdTy AllocId); /// Validate that all data references to function offsets are claimed by - /// recognized jump tables. Register externally referenced blocks as entry - /// points. Returns true if there are no unclaimed externally referenced - /// offsets. - bool validateExternallyReferencedOffsets(); + /// recognized jump tables. Returns true if there are no unclaimed externally + /// referenced offsets. + bool validateInternalRefDataRelocations(); /// Return all call site profile info for this function. IndirectCallSiteProfile &getAllCallSites() { return AllCallSites; } @@ -2574,6 +2583,10 @@ class BinaryFunction { /// Return true if the function is an AArch64 linker inserted veneer bool isAArch64Veneer() const; + /// Return true if the function signature matches veneer or it was established + /// to be a veneer. + bool isPossibleVeneer() const; + virtual ~BinaryFunction(); }; diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h index fe7905c33080..6d0ba466347c 100644 --- a/bolt/include/bolt/Core/MCPlusBuilder.h +++ b/bolt/include/bolt/Core/MCPlusBuilder.h @@ -1888,6 +1888,25 @@ class MCPlusBuilder { llvm_unreachable("not implemented"); } + /// Update operand of BTI instruction. + virtual void updateBTIVariant(MCInst &Inst, bool CallTarget, + bool JumpTarget) const { + llvm_unreachable("not implemented"); + } + + /// Checks if the indirect call / jump is accepted by the landing pad at the + /// start of the target BasicBlock. + virtual bool isCallCoveredByBTI(MCInst &Call, MCInst &Pad) const { + llvm_unreachable("not implemented"); + return false; + } + + /// Inserts a BTI landing pad to the start of the BB, that matches the + /// indirect call inst used to call the BB. + virtual void insertBTI(BinaryBasicBlock &BB, MCInst &Call) const { + llvm_unreachable("not implemented"); + } + /// Store \p Target absolute address to \p RegName virtual InstructionListType materializeAddress(const MCSymbol *Target, MCContext *Ctx, diff --git a/bolt/include/bolt/Passes/IdenticalCodeFolding.h b/bolt/include/bolt/Passes/IdenticalCodeFolding.h index f59e75c61860..1664c69de375 100644 --- a/bolt/include/bolt/Passes/IdenticalCodeFolding.h +++ b/bolt/include/bolt/Passes/IdenticalCodeFolding.h @@ -37,17 +37,19 @@ class IdenticalCodeFolding : public BinaryFunctionPass { Error runOnFunctions(BinaryContext &BC) override; private: + static constexpr uint64_t VTableAddressGranularity = 4; + /// Bit vector of memory addresses of vtables. llvm::SparseBitVector<> VTableBitVector; /// Return true if the memory address is in a vtable. bool isAddressInVTable(uint64_t Address) const { - return VTableBitVector.test(Address / 8); + return VTableBitVector.test(Address / VTableAddressGranularity); } /// Mark memory address of a vtable as used. void setAddressUsedInVTable(uint64_t Address) { - VTableBitVector.set(Address / 8); + VTableBitVector.set(Address / VTableAddressGranularity); } /// Scan symbol table and mark memory addresses of diff --git a/bolt/include/bolt/Passes/InsertNegateRAStatePass.h b/bolt/include/bolt/Passes/InsertNegateRAStatePass.h deleted file mode 100644 index 836948bf5e9c..000000000000 --- a/bolt/include/bolt/Passes/InsertNegateRAStatePass.h +++ /dev/null @@ -1,46 +0,0 @@ -//===- bolt/Passes/InsertNegateRAStatePass.cpp ----------------------------===// -// -// 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 InsertNegateRAStatePass class. -// -//===----------------------------------------------------------------------===// -#ifndef BOLT_PASSES_INSERT_NEGATE_RA_STATE_PASS -#define BOLT_PASSES_INSERT_NEGATE_RA_STATE_PASS - -#include "bolt/Passes/BinaryPasses.h" - -namespace llvm { -namespace bolt { - -class InsertNegateRAState : public BinaryFunctionPass { -public: - explicit InsertNegateRAState() : BinaryFunctionPass(false) {} - - const char *getName() const override { return "insert-negate-ra-state-pass"; } - - /// Pass entry point - Error runOnFunctions(BinaryContext &BC) override; - void runOnFunction(BinaryFunction &BF); - -private: - /// Because states are tracked as MCAnnotations on individual instructions, - /// newly inserted instructions do not have a state associated with them. - /// New states are "inherited" from the last known state. - void inferUnknownStates(BinaryFunction &BF); - - /// Support for function splitting: - /// if two consecutive BBs with Signed state are going to end up in different - /// functions (so are held by different FunctionFragments), we have to add a - /// OpNegateRAState to the beginning of the newly split function, so it starts - /// with a Signed state. - void coverFunctionFragmentStart(BinaryFunction &BF, FunctionFragment &FF); -}; - -} // namespace bolt -} // namespace llvm -#endif diff --git a/bolt/include/bolt/Passes/MarkRAStates.h b/bolt/include/bolt/Passes/MarkRAStates.h deleted file mode 100644 index 202f1dda2aad..000000000000 --- a/bolt/include/bolt/Passes/MarkRAStates.h +++ /dev/null @@ -1,38 +0,0 @@ -//===- bolt/Passes/MarkRAStates.cpp ---------------------------------===// -// -// 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 MarkRAStates class. -// -//===----------------------------------------------------------------------===// -#ifndef BOLT_PASSES_MARK_RA_STATES -#define BOLT_PASSES_MARK_RA_STATES - -#include "bolt/Passes/BinaryPasses.h" -#include - -namespace llvm { -namespace bolt { - -class MarkRAStates : public BinaryFunctionPass { - // setIgnored() is not thread-safe, but the pass is running on functions in - // parallel. - std::mutex IgnoreMutex; - -public: - explicit MarkRAStates() : BinaryFunctionPass(false) {} - - const char *getName() const override { return "mark-ra-states"; } - - /// Pass entry point - Error runOnFunctions(BinaryContext &BC) override; - bool runOnFunction(BinaryFunction &BF); -}; - -} // namespace bolt -} // namespace llvm -#endif diff --git a/bolt/include/bolt/Passes/PointerAuthCFIAnalyzer.h b/bolt/include/bolt/Passes/PointerAuthCFIAnalyzer.h new file mode 100644 index 000000000000..cc0b27f2593a --- /dev/null +++ b/bolt/include/bolt/Passes/PointerAuthCFIAnalyzer.h @@ -0,0 +1,39 @@ +//===- bolt/Passes/PointerAuthCFIAnalyzer.h -------------------------------===// +// +// 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 PointerAuthCFIAnalyzer class. +// +//===----------------------------------------------------------------------===// +#ifndef BOLT_PASSES_POINTER_AUTH_CFI_ANALYZER +#define BOLT_PASSES_POINTER_AUTH_CFI_ANALYZER + +#include "bolt/Passes/BinaryPasses.h" +#include + +namespace llvm { +namespace bolt { + +class PointerAuthCFIAnalyzer : public BinaryFunctionPass { + // setIgnored() is not thread-safe, but the pass is running on functions in + // parallel. + std::mutex IgnoreMutex; + +public: + explicit PointerAuthCFIAnalyzer(const cl::opt &PrintPass) + : BinaryFunctionPass(PrintPass) {} + + const char *getName() const override { return "pointer-auth-cfi-analyzer"; } + + /// Pass entry point + Error runOnFunctions(BinaryContext &BC) override; + bool runOnFunction(BinaryFunction &BF); +}; + +} // namespace bolt +} // namespace llvm +#endif diff --git a/bolt/include/bolt/Passes/PointerAuthCFIFixup.h b/bolt/include/bolt/Passes/PointerAuthCFIFixup.h new file mode 100644 index 000000000000..61b3e3c59f0f --- /dev/null +++ b/bolt/include/bolt/Passes/PointerAuthCFIFixup.h @@ -0,0 +1,68 @@ +//===- bolt/Passes/PointerAuthCFIFixup.h ----------------------------------===// +// +// 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 PointerAuthCFIFixup class. +// +//===----------------------------------------------------------------------===// +#ifndef BOLT_PASSES_POINTER_AUTH_CFI_FIXUP +#define BOLT_PASSES_POINTER_AUTH_CFI_FIXUP + +#include "bolt/Passes/BinaryPasses.h" + +namespace llvm { +namespace bolt { + +class PointerAuthCFIFixup : public BinaryFunctionPass { +public: + explicit PointerAuthCFIFixup(const cl::opt &PrintPass) + : BinaryFunctionPass(PrintPass) {} + + const char *getName() const override { return "pointer-auth-cfi-fixup"; } + + /// Pass entry point + Error runOnFunctions(BinaryContext &BC) override; + void runOnFunction(BinaryFunction &BF); + +private: + /// Because states are tracked as MCAnnotations on individual instructions, + /// newly inserted instructions do not have a state associated with them. + /// Uses fillUnknownStateInBB and fillUnknownStubs. + void inferUnknownStates(BinaryFunction &BF); + + /// Simple case: copy RAStates to unknown insts from previous inst. + /// If the first inst has unknown state, copy set it to the first known state. + /// Accounts for signing and authenticating insts. + void fillUnknownStateInBB(BinaryContext &BC, BinaryBasicBlock &BB); + + /// Fill in RAState in BasicBlocks consisting entirely of new instructions. + /// As of #160989, we have to copy the RAState from the previous BB in the + /// layout, because CFIs are already incorrect here. + void fillUnknownStubs(BinaryFunction &BF); + + /// Returns the first known RAState from \p BB, or std::nullopt if all are + /// unknown. + std::optional getFirstKnownRAState(BinaryContext &BC, + BinaryBasicBlock &BB); + + /// \p Return true if all instructions have unknown RAState. + bool isUnknownBlock(BinaryContext &BC, BinaryBasicBlock &BB); + + /// Set all instructions in \p BB to \p State. + void markUnknownBlock(BinaryContext &BC, BinaryBasicBlock &BB, bool State); + + /// Support for function splitting: + /// if two consecutive BBs with Signed state are going to end up in different + /// functions (so are held by different FunctionFragments), we have to add a + /// OpNegateRAState to the beginning of the newly split function, so it starts + /// with a Signed state. + void coverFunctionFragmentStart(BinaryFunction &BF, FunctionFragment &FF); +}; + +} // namespace bolt +} // namespace llvm +#endif diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h index 35abf6b4d4dd..5950b3c1630e 100644 --- a/bolt/include/bolt/Rewrite/RewriteInstance.h +++ b/bolt/include/bolt/Rewrite/RewriteInstance.h @@ -93,14 +93,23 @@ class RewriteInstance { /// section allocations if found. void discoverBOLTReserved(); + /// Check whether we should use DT_INIT or DT_INIT_ARRAY for instrumentation. + /// DT_INIT is preferred; DT_INIT_ARRAY is only used when no DT_INIT entry was + /// found. + Error discoverRtInitAddress(); + /// Check whether we should use DT_FINI or DT_FINI_ARRAY for instrumentation. /// DT_FINI is preferred; DT_FINI_ARRAY is only used when no DT_FINI entry was /// found. Error discoverRtFiniAddress(); + /// If DT_INIT_ARRAY is used for instrumentation, update the relocation of its + /// first entry to point to the instrumentation library's init address. + Error updateRtInitReloc(); + /// If DT_FINI_ARRAY is used for instrumentation, update the relocation of its /// first entry to point to the instrumentation library's fini address. - void updateRtFiniReloc(); + Error updateRtFiniReloc(); /// Create and initialize metadata rewriters for this instance. void initializeMetadataManager(); diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp index 6a285f5538db..a980ef7b99c3 100644 --- a/bolt/lib/Core/BinaryContext.cpp +++ b/bolt/lib/Core/BinaryContext.cpp @@ -531,20 +531,40 @@ BinaryContext::handleAddressRef(uint64_t Address, BinaryFunction &BF, } MCSymbol *BinaryContext::handleExternalBranchTarget(uint64_t Address, - BinaryFunction &BF) { - if (BF.isInConstantIsland(Address)) { - BF.setIgnored(); - this->outs() << "BOLT-WARNING: ignoring entry point at address 0x" - << Twine::utohexstr(Address) - << " in constant island of function " << BF << '\n'; - return nullptr; + BinaryFunction &Source, + BinaryFunction &Target) { + const uint64_t Offset = Address - Target.getAddress(); + assert(Offset < Target.getSize() && + "Address should be inside the referenced function"); + + bool IsValid = true; + if (Source.NeedBranchValidation) { + if (Target.CurrentState == BinaryFunction::State::Disassembled && + !Target.getInstructionAtOffset(Offset)) { + this->errs() + << "BOLT-WARNING: corrupted control flow detected in function " + << Source + << ": an external branch/call targets an invalid instruction " + << "in function " << Target << " at address 0x" + << Twine::utohexstr(Address) << "; ignoring both functions\n"; + IsValid = false; + } + if (Target.isInConstantIsland(Address)) { + this->errs() << "BOLT-WARNING: ignoring entry point at address 0x" + << Twine::utohexstr(Address) + << " in constant island of function " << Target << '\n'; + IsValid = false; + } } - const uint64_t Offset = Address - BF.getAddress(); - assert(Offset < BF.getSize() && - "Address should be inside the referenced function"); + if (!IsValid) { + Source.NeedBranchValidation = false; + Source.setIgnored(); + Target.setIgnored(); + return nullptr; + } - return Offset ? BF.addEntryPointAtOffset(Offset) : BF.getSymbol(); + return Offset ? Target.addEntryPointAtOffset(Offset) : Target.getSymbol(); } MemoryContentsType BinaryContext::analyzeMemoryAt(uint64_t Address, @@ -1433,13 +1453,11 @@ void BinaryContext::processInterproceduralReferences() { // Create an extra entry point if needed. Can also render the target // function ignored if the reference is invalid. - handleExternalBranchTarget(Address, *TargetFunction); + handleExternalBranchTarget(Address, Function, *TargetFunction); continue; } - // Check if address falls in function padding space - this could be - // unmarked data in code. In this case adjust the padding space size. ErrorOr Section = getSectionForAddress(Address); assert(Section && "cannot get section for referenced address"); @@ -1451,7 +1469,7 @@ void BinaryContext::processInterproceduralReferences() { if (SectionName == ".plt" || SectionName == ".plt.got") continue; - // Check if it is aarch64 veneer written at Address + // Check if it is aarch64 veneer written at Address. if (isAArch64() && handleAArch64Veneer(Address)) continue; @@ -1463,6 +1481,8 @@ void BinaryContext::processInterproceduralReferences() { exit(1); } + // Check if the address falls into the function padding space - this could + // be an unmarked data in code. In this case, adjust the padding space size. TargetFunction = getBinaryFunctionContainingAddress(Address, /*CheckPastEnd=*/false, /*UseMaxSize=*/true); @@ -1868,6 +1888,9 @@ void BinaryContext::preprocessDebugInfo() { preprocessDWODebugInfo(); + // Check if required DWO files are missing. + uint64_t NumMissingDWOs = 0; + // Populate MCContext with DWARF files from all units. StringRef GlobalPrefix = AsmInfo->getPrivateGlobalPrefix(); for (const std::unique_ptr &CU : DwCtx->compile_units()) { @@ -1889,19 +1912,23 @@ void BinaryContext::preprocessDebugInfo() { std::optional Checksum; if (LineTable->Prologue.ContentTypes.HasMD5) Checksum = LineTable->Prologue.FileNames[0].Checksum; - std::optional Name = + const char *Name = dwarf::toString(CU->getUnitDIE().find(dwarf::DW_AT_name), nullptr); if (std::optional DWOID = CU->getDWOId()) { auto Iter = DWOCUs.find(*DWOID); if (Iter == DWOCUs.end()) { - this->errs() << "BOLT-ERROR: DWO CU was not found for " << Name - << '\n'; - exit(1); + const char *DWOName = + dwarf::toString(CU->getUnitDIE().find(dwarf::DW_AT_dwo_name), + ""); + this->errs() << "BOLT-ERROR: unable to load " << DWOName + << " for DWO_id 0x" << Twine::utohexstr(*DWOID) << '\n'; + NumMissingDWOs++; + continue; } Name = dwarf::toString( Iter->second->getUnitDIE().find(dwarf::DW_AT_name), nullptr); } - BinaryLineTable.setRootFile(CU->getCompilationDir(), *Name, Checksum, + BinaryLineTable.setRootFile(CU->getCompilationDir(), Name, Checksum, std::nullopt); } @@ -1936,6 +1963,14 @@ void BinaryContext::preprocessDebugInfo() { DwarfVersion)); } } + + if (NumMissingDWOs) { + this->errs() << "BOLT-ERROR: " << NumMissingDWOs + << " required DWO file(s) not found. Unable to update debug" + " info. Use --comp-dir-override to locate the file(s) or" + " --update-debug-sections=0 to remove debug info\n"; + exit(1); + } } bool BinaryContext::shouldEmit(const BinaryFunction &Function) const { @@ -2563,6 +2598,10 @@ BinaryContext::createInstructionPatch(uint64_t Address, PBF->addBasicBlock()->addInstructions(Instructions); PBF->setIsPatch(true); + // Patch functions have to be emitted each into their unique section. + PBF->setCodeSectionName( + BinaryFunction::buildCodeSectionName(PBF->getOneName(), *this)); + // Don't create symbol table entry if the name wasn't specified. if (Name.str().empty()) PBF->setAnonymous(true); diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp index a5fdf79a737f..dc83b9e8ed76 100644 --- a/bolt/lib/Core/BinaryFunction.cpp +++ b/bolt/lib/Core/BinaryFunction.cpp @@ -1038,8 +1038,10 @@ MCSymbol *BinaryFunction::getOrCreateLocalLabel(uint64_t Address) { // For AArch64, check if this address is part of a constant island. if (BC.isAArch64()) { - if (MCSymbol *IslandSym = getOrCreateIslandAccess(Address)) + if (MCSymbol *IslandSym = getOrCreateIslandAccess(Address)) { + Labels[Offset] = IslandSym; return IslandSym; + } } if (Offset == getSize()) @@ -1408,9 +1410,7 @@ Error BinaryFunction::disassemble() { // A recursive call. Calls to internal blocks are handled by // ValidateInternalCalls pass. TargetSymbol = getSymbol(); - } - - if (!TargetSymbol) { + } else { // Create either local label or external symbol. if (containsAddress(TargetAddress)) { TargetSymbol = getOrCreateLocalLabel(TargetAddress); @@ -1694,7 +1694,7 @@ bool BinaryFunction::scanExternalRefs() { // Get a reference symbol for the function when address is a valid code // reference. BranchTargetSymbol = - BC.handleExternalBranchTarget(TargetAddress, *TargetFunction); + BC.handleExternalBranchTarget(TargetAddress, *this, *TargetFunction); if (!BranchTargetSymbol) continue; } @@ -1902,6 +1902,36 @@ bool BinaryFunction::scanExternalRefs() { return Success; } +bool BinaryFunction::validateInternalBranches() { + if (!isSimple() || TrapsOnEntry) + return true; + + for (const auto &KV : Labels) { + MCSymbol *Label = KV.second; + if (getSecondaryEntryPointSymbol(Label)) + continue; + + const uint32_t Offset = KV.first; + // Skip empty functions and out-of-bounds offsets, + // as they may not be disassembled. + if (!Offset || (Offset > getSize())) + continue; + + if (!getInstructionAtOffset(Offset) || + isInConstantIsland(getAddress() + Offset)) { + BC.errs() << "BOLT-WARNING: corrupted control flow detected in function " + << *this << ": an internal branch/call targets an invalid " + << "instruction at address 0x" + << Twine::utohexstr(getAddress() + Offset) + << "; ignoring this function\n"; + setIgnored(); + return false; + } + } + + return true; +} + void BinaryFunction::postProcessEntryPoints() { if (!isSimple()) return; @@ -2043,41 +2073,47 @@ void BinaryFunction::postProcessJumpTables() { } } -bool BinaryFunction::validateExternallyReferencedOffsets() { - SmallPtrSet JTTargets; - for (const JumpTable *JT : llvm::make_second_range(JumpTables)) - JTTargets.insert_range(JT->Entries); +bool BinaryFunction::validateInternalRefDataRelocations() { + if (InternalRefDataRelocations.empty()) + return true; - bool HasUnclaimedReference = false; - for (uint64_t Destination : ExternallyReferencedOffsets) { - // Ignore __builtin_unreachable(). - if (Destination == getSize()) - continue; - // Ignore constant islands - if (isInConstantIsland(Destination + getAddress())) - continue; + // Rely on the user hint that all data refs are valid and only used as + // destinations by indirect branch in the same function. + if (opts::StrictMode) + return true; - if (BinaryBasicBlock *BB = getBasicBlockAtOffset(Destination)) { - // Check if the externally referenced offset is a recognized jump table - // target. - if (JTTargets.contains(BB->getLabel())) - continue; + DenseSet UnclaimedRelocations(InternalRefDataRelocations); + for (const JumpTable *JT : llvm::make_second_range(JumpTables)) { + uint64_t EntryAddress = JT->getAddress(); + while (EntryAddress < JT->getAddress() + JT->getSize()) { + UnclaimedRelocations.erase(EntryAddress); + EntryAddress += JT->EntrySize; + } + } - if (opts::Verbosity >= 1) { - BC.errs() << "BOLT-WARNING: unclaimed data to code reference (possibly " - << "an unrecognized jump table entry) to " << BB->getName() - << " in " << *this << "\n"; - } - auto L = BC.scopeLock(); - addEntryPoint(*BB); - } else { - BC.errs() << "BOLT-WARNING: unknown data to code reference to offset " - << Twine::utohexstr(Destination) << " in " << *this << "\n"; - setIgnored(); + if (UnclaimedRelocations.empty()) + return true; + + BC.errs() << "BOLT-WARNING: " << UnclaimedRelocations.size() + << " unclaimed data relocation" + << (UnclaimedRelocations.size() > 1 ? "s" : "") + << " remain against function " << *this; + if (opts::Verbosity) { + BC.errs() << ":\n"; + for (uint64_t RelocationAddress : UnclaimedRelocations) { + const Relocation *Relocation = BC.getRelocationAt(RelocationAddress); + BC.errs() << " "; + if (Relocation) + BC.errs() << *Relocation; + else + BC.errs() << ""; + BC.errs() << '\n'; } - HasUnclaimedReference = true; + } else { + BC.errs() << ". Re-run with -v=1 to see the list\n"; } - return !HasUnclaimedReference; + + return false; } bool BinaryFunction::postProcessIndirectBranches( @@ -2202,14 +2238,6 @@ bool BinaryFunction::postProcessIndirectBranches( LastIndirectJumpBB->updateJumpTableSuccessors(); } - // Validate that all data references to function offsets are claimed by - // recognized jump tables. Register externally referenced blocks as entry - // points. - if (!opts::StrictMode && hasInternalReference()) { - if (!validateExternallyReferencedOffsets()) - return false; - } - if (HasUnknownControlFlow && !BC.HasRelocations) return false; @@ -2498,12 +2526,18 @@ Error BinaryFunction::buildCFG(MCPlusBuilder::AllocatorIdTy AllocatorId) { CurrentState = State::CFG; // Make any necessary adjustments for indirect branches. - if (!postProcessIndirectBranches(AllocatorId)) { - if (opts::Verbosity) { - BC.errs() << "BOLT-WARNING: failed to post-process indirect branches for " - << *this << '\n'; - } + bool ValidCFG = postProcessIndirectBranches(AllocatorId); + if (!ValidCFG && opts::Verbosity) { + BC.errs() << "BOLT-WARNING: failed to post-process indirect branches for " + << *this << '\n'; + } + + // Validate that all data references to function offsets are claimed by + // recognized jump tables. + if (ValidCFG) + ValidCFG = validateInternalRefDataRelocations(); + if (!ValidCFG) { if (BC.isAArch64()) PreserveNops = BC.HasRelocations; @@ -4795,6 +4829,11 @@ bool BinaryFunction::isAArch64Veneer() const { return true; } +bool BinaryFunction::isPossibleVeneer() const { + return BC.isAArch64() && + (isAArch64Veneer() || getOneName().starts_with("__AArch64")); +} + void BinaryFunction::addRelocation(uint64_t Address, MCSymbol *Symbol, uint32_t RelType, uint64_t Addend, uint64_t Value) { diff --git a/bolt/lib/Core/Exceptions.cpp b/bolt/lib/Core/Exceptions.cpp index 27656c7b3cad..9c33a7c648da 100644 --- a/bolt/lib/Core/Exceptions.cpp +++ b/bolt/lib/Core/Exceptions.cpp @@ -572,7 +572,7 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const { if (Function.getBinaryContext().isAArch64()) { // Support for pointer authentication: // We need to annotate instructions that modify the RA State, to work - // out the state of each instruction in MarkRAStates Pass. + // out the state of each instruction in PointerAuthCFIAnalyzer Pass. if (Offset != 0) Function.setInstModifiesRAState(DW_CFA_remember_state, Offset); } @@ -583,7 +583,7 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const { if (Function.getBinaryContext().isAArch64()) { // Support for pointer authentication: // We need to annotate instructions that modify the RA State, to work - // out the state of each instruction in MarkRAStates Pass. + // out the state of each instruction in PointerAuthCFIAnalyzer Pass. if (Offset != 0) Function.setInstModifiesRAState(DW_CFA_restore_state, Offset); } @@ -652,7 +652,7 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const { // BasicBlocks, which changes during optimizations. Instead of adding // OpNegateRAState CFIs, an annotation is added to the instruction, to // mark that the instruction modifies the RA State. The actual state for - // instructions are worked out in MarkRAStates based on these + // instructions are worked out in PointerAuthCFIAnalyzer based on these // annotations. if (Offset != 0) Function.setInstModifiesRAState(DW_CFA_AARCH64_negate_ra_state, @@ -660,7 +660,7 @@ bool CFIReaderWriter::fillCFIInfoFor(BinaryFunction &Function) const { else // We cannot Annotate an instruction at Offset == 0. // Instead, we save the initial (Signed) state, and push it to - // MarkRAStates' RAStateStack. + // PointerAuthCFIAnalyzer's RAStateStack. Function.setInitialRAState(true); break; } diff --git a/bolt/lib/Passes/BinaryPasses.cpp b/bolt/lib/Passes/BinaryPasses.cpp index 1d187de11c35..144cbc573ed0 100644 --- a/bolt/lib/Passes/BinaryPasses.cpp +++ b/bolt/lib/Passes/BinaryPasses.cpp @@ -1276,13 +1276,6 @@ Error SimplifyRODataLoads::runOnFunctions(BinaryContext &BC) { } Error AssignSections::runOnFunctions(BinaryContext &BC) { - for (BinaryFunction *Function : BC.getInjectedBinaryFunctions()) { - if (!Function->isPatch()) { - Function->setCodeSectionName(BC.getInjectedCodeSectionName()); - Function->setColdCodeSectionName(BC.getInjectedColdCodeSectionName()); - } - } - // In non-relocation mode functions have pre-assigned section names. if (!BC.HasRelocations) return Error::success(); diff --git a/bolt/lib/Passes/CMakeLists.txt b/bolt/lib/Passes/CMakeLists.txt index 3197e62faad2..ec012f05cc49 100644 --- a/bolt/lib/Passes/CMakeLists.txt +++ b/bolt/lib/Passes/CMakeLists.txt @@ -17,18 +17,18 @@ add_llvm_library(LLVMBOLTPasses IdenticalCodeFolding.cpp IndirectCallPromotion.cpp Inliner.cpp - InsertNegateRAStatePass.cpp Instrumentation.cpp JTFootprintReduction.cpp LongJmp.cpp LoopInversionPass.cpp LivenessAnalysis.cpp MCF.cpp - MarkRAStates.cpp PatchEntries.cpp PAuthGadgetScanner.cpp PettisAndHansen.cpp PLTCall.cpp + PointerAuthCFIAnalyzer.cpp + PointerAuthCFIFixup.cpp ProfileQualityStats.cpp RegAnalysis.cpp RegReAssign.cpp diff --git a/bolt/lib/Passes/IdenticalCodeFolding.cpp b/bolt/lib/Passes/IdenticalCodeFolding.cpp index 8923562776cc..c5c33b7cde8d 100644 --- a/bolt/lib/Passes/IdenticalCodeFolding.cpp +++ b/bolt/lib/Passes/IdenticalCodeFolding.cpp @@ -377,9 +377,11 @@ namespace bolt { void IdenticalCodeFolding::initVTableReferences(const BinaryContext &BC) { for (const auto &[Address, Data] : BC.getBinaryData()) { // Filter out all symbols that are not vtables. - if (!Data->getName().starts_with("_ZTV")) + if (!Data->getName().starts_with("_ZTV") && // vtable + !Data->getName().starts_with("_ZTCN")) // construction vtable continue; - for (uint64_t I = Address, End = I + Data->getSize(); I < End; I += 8) + for (uint64_t I = Address, End = I + Data->getSize(); I < End; + I += VTableAddressGranularity) setAddressUsedInVTable(I); } } @@ -437,8 +439,9 @@ void IdenticalCodeFolding::markFunctionsUnsafeToFold(BinaryContext &BC) { NamedRegionTimer MarkFunctionsUnsafeToFoldTimer( "markFunctionsUnsafeToFold", "markFunctionsUnsafeToFold", "ICF breakdown", "ICF breakdown", opts::TimeICF); - if (!BC.isX86()) - BC.outs() << "BOLT-WARNING: safe ICF is only supported for x86\n"; + if (!BC.isX86() && !BC.isAArch64()) + BC.outs() + << "BOLT-WARNING: safe ICF is only supported for x86 and AArch64\n"; analyzeDataRelocations(BC); analyzeFunctions(BC); } diff --git a/bolt/lib/Passes/Inliner.cpp b/bolt/lib/Passes/Inliner.cpp index 5a7d02a34b4d..0740fcef9102 100644 --- a/bolt/lib/Passes/Inliner.cpp +++ b/bolt/lib/Passes/Inliner.cpp @@ -491,6 +491,32 @@ bool Inliner::inlineCallsInFunction(BinaryFunction &Function) { } } + // AArch64 BTI: + // If the callee has an indirect tailcall (BR), we would transform it to + // an indirect call (BLR) in InlineCall. Because of this, we would have to + // update the BTI at the target of the tailcall. However, these targets + // are not known. Instead, we skip inlining blocks with indirect + // tailcalls. + auto HasIndirectTailCall = [&](const BinaryFunction &BF) -> bool { + for (const auto &BB : BF) { + for (const auto &II : BB) { + if (BC.MIB->isIndirectBranch(II) && BC.MIB->isTailCall(II)) { + return true; + } + } + } + return false; + }; + + if (BC.isAArch64() && BC.usesBTI() && + HasIndirectTailCall(*TargetFunction)) { + ++InstIt; + LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Skipping inlining block with tailcall" + << " in " << Function << " : " << BB->getName() + << " to keep BTIs consistent.\n"); + continue; + } + LLVM_DEBUG(dbgs() << "BOLT-DEBUG: inlining call to " << *TargetFunction << " in " << Function << " : " << BB->getName() << ". Count: " << BB->getKnownExecutionCount() diff --git a/bolt/lib/Passes/InsertNegateRAStatePass.cpp b/bolt/lib/Passes/InsertNegateRAStatePass.cpp deleted file mode 100644 index 775b7795e77c..000000000000 --- a/bolt/lib/Passes/InsertNegateRAStatePass.cpp +++ /dev/null @@ -1,167 +0,0 @@ -//===- bolt/Passes/InsertNegateRAStatePass.cpp ----------------------------===// -// -// 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 InsertNegateRAStatePass class. It inserts -// OpNegateRAState CFIs to places where the state of two consecutive -// instructions are different. -// -//===----------------------------------------------------------------------===// -#include "bolt/Passes/InsertNegateRAStatePass.h" -#include "bolt/Core/BinaryFunction.h" -#include "bolt/Core/ParallelUtilities.h" -#include - -using namespace llvm; - -namespace llvm { -namespace bolt { - -static bool PassFailed = false; - -void InsertNegateRAState::runOnFunction(BinaryFunction &BF) { - if (PassFailed) - return; - - BinaryContext &BC = BF.getBinaryContext(); - - if (BF.getState() == BinaryFunction::State::Empty) - return; - - if (BF.getState() != BinaryFunction::State::CFG && - BF.getState() != BinaryFunction::State::CFG_Finalized) { - BC.outs() << "BOLT-INFO: no CFG for " << BF.getPrintName() - << " in InsertNegateRAStatePass\n"; - return; - } - - inferUnknownStates(BF); - - for (FunctionFragment &FF : BF.getLayout().fragments()) { - coverFunctionFragmentStart(BF, FF); - bool FirstIter = true; - bool PrevRAState = false; - // As this pass runs after function splitting, we should only check - // consecutive instructions inside FunctionFragments. - for (BinaryBasicBlock *BB : FF) { - for (auto It = BB->begin(); It != BB->end(); ++It) { - MCInst &Inst = *It; - if (BC.MIB->isCFI(Inst)) - continue; - auto RAState = BC.MIB->getRAState(Inst); - if (!RAState) { - BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates " - << " in function " << BF.getPrintName() << "\n"; - PassFailed = true; - return; - } - if (!FirstIter) { - // Consecutive instructions with different RAState means we need to - // add a OpNegateRAState. - if (*RAState != PrevRAState) - It = BF.addCFIInstruction( - BB, It, MCCFIInstruction::createNegateRAState(nullptr)); - } else { - FirstIter = false; - } - PrevRAState = *RAState; - } - } - } -} - -void InsertNegateRAState::coverFunctionFragmentStart(BinaryFunction &BF, - FunctionFragment &FF) { - BinaryContext &BC = BF.getBinaryContext(); - if (FF.empty()) - return; - // Find the first BB in the FF which has Instructions. - // BOLT can generate empty BBs at function splitting which are only used as - // target labels. We should add the negate-ra-state CFI to the first - // non-empty BB. - auto *FirstNonEmpty = - std::find_if(FF.begin(), FF.end(), [](BinaryBasicBlock *BB) { - // getFirstNonPseudo returns BB.end() if it does not find any - // Instructions. - return BB->getFirstNonPseudo() != BB->end(); - }); - // If a function is already split in the input, the first FF can also start - // with Signed state. This covers that scenario as well. - auto II = (*FirstNonEmpty)->getFirstNonPseudo(); - auto RAState = BC.MIB->getRAState(*II); - if (!RAState) { - BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates " - << " in function " << BF.getPrintName() << "\n"; - PassFailed = true; - return; - } - if (*RAState) - BF.addCFIInstruction(*FirstNonEmpty, II, - MCCFIInstruction::createNegateRAState(nullptr)); -} - -void InsertNegateRAState::inferUnknownStates(BinaryFunction &BF) { - BinaryContext &BC = BF.getBinaryContext(); - bool FirstIter = true; - MCInst PrevInst; - for (BinaryBasicBlock &BB : BF) { - for (MCInst &Inst : BB) { - if (BC.MIB->isCFI(Inst)) - continue; - - auto RAState = BC.MIB->getRAState(Inst); - if (!FirstIter && !RAState) { - if (BC.MIB->isPSignOnLR(PrevInst)) - RAState = true; - else if (BC.MIB->isPAuthOnLR(PrevInst)) - RAState = false; - else { - auto PrevRAState = BC.MIB->getRAState(PrevInst); - RAState = PrevRAState ? *PrevRAState : false; - } - BC.MIB->setRAState(Inst, *RAState); - } else { - FirstIter = false; - if (!RAState) - BC.MIB->setRAState(Inst, BF.getInitialRAState()); - } - PrevInst = Inst; - } - } -} - -Error InsertNegateRAState::runOnFunctions(BinaryContext &BC) { - std::atomic FunctionsModified{0}; - ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) { - FunctionsModified++; - runOnFunction(BF); - }; - - ParallelUtilities::PredicateTy SkipPredicate = [&](const BinaryFunction &BF) { - // We can skip functions which did not include negate-ra-state CFIs. This - // includes code using pac-ret hardening as well, if the binary is - // compiled with `-fno-exceptions -fno-unwind-tables - // -fno-asynchronous-unwind-tables` - return !BF.containedNegateRAState() || BF.isIgnored(); - }; - - ParallelUtilities::runOnEachFunction( - BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun, - SkipPredicate, "InsertNegateRAStatePass"); - - BC.outs() << "BOLT-INFO: rewritten pac-ret DWARF info in " - << FunctionsModified << " out of " << BC.getBinaryFunctions().size() - << " functions " - << format("(%.2lf%%).\n", (100.0 * FunctionsModified) / - BC.getBinaryFunctions().size()); - if (PassFailed) - return createFatalBOLTError(""); - return Error::success(); -} - -} // end namespace bolt -} // end namespace llvm diff --git a/bolt/lib/Passes/MarkRAStates.cpp b/bolt/lib/Passes/MarkRAStates.cpp deleted file mode 100644 index 51075be0e1ac..000000000000 --- a/bolt/lib/Passes/MarkRAStates.cpp +++ /dev/null @@ -1,145 +0,0 @@ -//===- bolt/Passes/MarkRAStates.cpp ---------------------------------===// -// -// 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 MarkRAStates class. -// Three CFIs have an influence on the RA State of an instruction: -// - NegateRAState flips the RA State, -// - RememberState pushes the RA State to a stack, -// - RestoreState pops the RA State from the stack. -// These are saved as MCAnnotations on instructions they refer to at CFI -// reading (in CFIReaderWriter::fillCFIInfoFor). In this pass, we can work out -// the RA State of each instruction, and save it as new MCAnnotations. The new -// annotations are Signing, Signed, Authenticating and Unsigned. After -// optimizations, .cfi_negate_ra_state CFIs are added to the places where the -// state changes in InsertNegateRAStatePass. -// -//===----------------------------------------------------------------------===// -#include "bolt/Passes/MarkRAStates.h" -#include "bolt/Core/BinaryFunction.h" -#include "bolt/Core/ParallelUtilities.h" -#include -#include -#include - -using namespace llvm; - -namespace llvm { -namespace bolt { - -bool MarkRAStates::runOnFunction(BinaryFunction &BF) { - - BinaryContext &BC = BF.getBinaryContext(); - - for (const BinaryBasicBlock &BB : BF) { - for (const MCInst &Inst : BB) { - if ((BC.MIB->isPSignOnLR(Inst) || - (BC.MIB->isPAuthOnLR(Inst) && !BC.MIB->isPAuthAndRet(Inst))) && - !BC.MIB->hasNegateRAState(Inst)) { - // Not all functions have .cfi_negate_ra_state in them. But if one does, - // we expect psign/pauth instructions to have the hasNegateRAState - // annotation. - BC.outs() << "BOLT-INFO: inconsistent RAStates in function " - << BF.getPrintName() - << ": ptr sign/auth inst without .cfi_negate_ra_state\n"; - std::lock_guard Lock(IgnoreMutex); - BF.setIgnored(); - return false; - } - } - } - - bool RAState = BF.getInitialRAState(); - std::stack RAStateStack; - RAStateStack.push(RAState); - - for (BinaryBasicBlock &BB : BF) { - for (MCInst &Inst : BB) { - if (BC.MIB->isCFI(Inst)) - continue; - - if (BC.MIB->isPSignOnLR(Inst)) { - if (RAState) { - // RA signing instructions should only follow unsigned RA state. - BC.outs() << "BOLT-INFO: inconsistent RAStates in function " - << BF.getPrintName() - << ": ptr signing inst encountered in Signed RA state\n"; - std::lock_guard Lock(IgnoreMutex); - BF.setIgnored(); - return false; - } - } else if (BC.MIB->isPAuthOnLR(Inst)) { - if (!RAState) { - // RA authenticating instructions should only follow signed RA state. - BC.outs() << "BOLT-INFO: inconsistent RAStates in function " - << BF.getPrintName() - << ": ptr authenticating inst encountered in Unsigned RA " - "state\n"; - std::lock_guard Lock(IgnoreMutex); - BF.setIgnored(); - return false; - } - } - - BC.MIB->setRAState(Inst, RAState); - - // Updating RAState. All updates are valid from the next instruction. - // Because the same instruction can have remember and restore, the order - // here is relevant. This is the reason to loop over Annotations instead - // of just checking each in a predefined order. - for (unsigned int Idx = 0; Idx < Inst.getNumOperands(); Idx++) { - std::optional Annotation = - BC.MIB->getAnnotationAtOpIndex(Inst, Idx); - if (!Annotation) - continue; - if (Annotation == MCPlus::MCAnnotation::kNegateState) - RAState = !RAState; - else if (Annotation == MCPlus::MCAnnotation::kRememberState) - RAStateStack.push(RAState); - else if (Annotation == MCPlus::MCAnnotation::kRestoreState) { - RAState = RAStateStack.top(); - RAStateStack.pop(); - } - } - } - } - return true; -} - -Error MarkRAStates::runOnFunctions(BinaryContext &BC) { - std::atomic FunctionsIgnored{0}; - ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) { - if (!runOnFunction(BF)) { - FunctionsIgnored++; - } - }; - - ParallelUtilities::PredicateTy SkipPredicate = [&](const BinaryFunction &BF) { - // We can skip functions which did not include negate-ra-state CFIs. This - // includes code using pac-ret hardening as well, if the binary is - // compiled with `-fno-exceptions -fno-unwind-tables - // -fno-asynchronous-unwind-tables` - return !BF.containedNegateRAState() || BF.isIgnored(); - }; - - int Total = llvm::count_if(BC.getBinaryFunctions(), [&](auto &P) { - return P.second.containedNegateRAState() && !P.second.isIgnored(); - }); - - ParallelUtilities::runOnEachFunction( - BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun, - SkipPredicate, "MarkRAStates"); - BC.outs() << "BOLT-INFO: MarkRAStates ran on " << Total - << " functions. Ignored " << FunctionsIgnored << " functions " - << format("(%.2lf%%)", (100.0 * FunctionsIgnored) / Total) - << " because of CFI inconsistencies\n"; - - return Error::success(); -} - -} // end namespace bolt -} // end namespace llvm diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp index 01b350b2f11f..d38a7fadb076 100644 --- a/bolt/lib/Passes/PAuthGadgetScanner.cpp +++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp @@ -547,7 +547,7 @@ class SrcSafetyAnalysis { // Being trusted is a strictly stronger property than being // safe-to-dereference. - assert(!Next.TrustedRegs.test(Next.SafeToDerefRegs) && + assert(Next.TrustedRegs.subsetOf(Next.SafeToDerefRegs) && "SafeToDerefRegs should contain all TrustedRegs"); return Next; diff --git a/bolt/lib/Passes/PointerAuthCFIAnalyzer.cpp b/bolt/lib/Passes/PointerAuthCFIAnalyzer.cpp new file mode 100644 index 000000000000..697b1bbbc5e5 --- /dev/null +++ b/bolt/lib/Passes/PointerAuthCFIAnalyzer.cpp @@ -0,0 +1,173 @@ +//===- bolt/Passes/PointerAuthCFIAnalyzer.cpp -----------------------------===// +// +// 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 PointerAuthCFIAnalyzer class. +// Three CFIs have an influence on the RA State of an instruction: +// - NegateRAState flips the RA State, +// - RememberState pushes the RA State to a stack, +// - RestoreState pops the RA State from the stack. +// These are saved as MCAnnotations on instructions they refer to at CFI +// reading (in CFIReaderWriter::fillCFIInfoFor). In this pass, we can work out +// the RA State of each instruction, and save it as new MCAnnotations. The new +// annotations are Signing, Signed, Authenticating and Unsigned. After +// optimizations, .cfi_negate_ra_state CFIs are added to the places where the +// state changes in PointerAuthCFIFixup. +// +//===----------------------------------------------------------------------===// +#include "bolt/Passes/PointerAuthCFIAnalyzer.h" +#include "bolt/Core/BinaryFunction.h" +#include "bolt/Core/ParallelUtilities.h" +#include +#include +#include + +using namespace llvm; + +namespace opts { +extern llvm::cl::opt Verbosity; +} // namespace opts + +namespace llvm { +namespace bolt { + +bool PointerAuthCFIAnalyzer::runOnFunction(BinaryFunction &BF) { + + BinaryContext &BC = BF.getBinaryContext(); + + for (const BinaryBasicBlock &BB : BF) { + for (const MCInst &Inst : BB) { + if ((BC.MIB->isPSignOnLR(Inst) || + (BC.MIB->isPAuthOnLR(Inst) && !BC.MIB->isPAuthAndRet(Inst))) && + !BC.MIB->hasNegateRAState(Inst)) { + // Not all functions have .cfi_negate_ra_state in them. But if one does, + // we expect psign/pauth instructions to have the hasNegateRAState + // annotation. + if (opts::Verbosity >= 1) + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() + << ": ptr sign/auth inst without .cfi_negate_ra_state\n"; + std::lock_guard Lock(IgnoreMutex); + BF.setIgnored(); + return false; + } + } + } + + bool RAState = BF.getInitialRAState(); + std::stack RAStateStack; + RAStateStack.push(RAState); + + for (BinaryBasicBlock &BB : BF) { + for (MCInst &Inst : BB) { + if (BC.MIB->isCFI(Inst)) + continue; + + if (BC.MIB->isPSignOnLR(Inst)) { + if (RAState) { + // RA signing instructions should only follow unsigned RA state. + if (opts::Verbosity >= 1) + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() + << ": ptr signing inst encountered in Signed RA state\n"; + std::lock_guard Lock(IgnoreMutex); + BF.setIgnored(); + return false; + } + } else if (BC.MIB->isPAuthOnLR(Inst)) { + if (!RAState) { + // RA authenticating instructions should only follow signed RA state. + if (opts::Verbosity >= 1) + BC.outs() << "BOLT-INFO: inconsistent RAStates in function " + << BF.getPrintName() + << ": ptr authenticating inst encountered in Unsigned RA " + "state\n"; + std::lock_guard Lock(IgnoreMutex); + BF.setIgnored(); + return false; + } + } + + BC.MIB->setRAState(Inst, RAState); + + // Updating RAState. All updates are valid from the next instruction. + // Because the same instruction can have remember and restore, the order + // here is relevant. This is the reason to loop over Annotations instead + // of just checking each in a predefined order. + for (unsigned int Idx = 0; Idx < Inst.getNumOperands(); Idx++) { + std::optional Annotation = + BC.MIB->getAnnotationAtOpIndex(Inst, Idx); + if (!Annotation) + continue; + if (Annotation == MCPlus::MCAnnotation::kNegateState) + RAState = !RAState; + else if (Annotation == MCPlus::MCAnnotation::kRememberState) + RAStateStack.push(RAState); + else if (Annotation == MCPlus::MCAnnotation::kRestoreState) { + RAState = RAStateStack.top(); + RAStateStack.pop(); + } + } + } + } + return true; +} + +Error PointerAuthCFIAnalyzer::runOnFunctions(BinaryContext &BC) { + std::atomic FunctionsIgnored{0}; + ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) { + if (!runOnFunction(BF)) { + FunctionsIgnored++; + } + }; + + ParallelUtilities::PredicateTy SkipPredicate = [&](const BinaryFunction &BF) { + // We can skip functions which did not include negate-ra-state CFIs. This + // includes code using pac-ret hardening as well, if the binary is + // compiled with `-fno-exceptions -fno-unwind-tables + // -fno-asynchronous-unwind-tables` + return !BF.containedNegateRAState() || BF.isIgnored(); + }; + + int Total = llvm::count_if(BC.getBinaryFunctions(), [&](auto &P) { + return P.second.containedNegateRAState() && !P.second.isIgnored(); + }); + + if (Total == 0) + return Error::success(); + + ParallelUtilities::runOnEachFunction( + BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun, + SkipPredicate, "PointerAuthCFIAnalyzer"); + + float IgnoredPercent = (100.0 * FunctionsIgnored) / Total; + BC.outs() << "BOLT-INFO: PointerAuthCFIAnalyzer ran on " << Total + << " functions. Ignored " << FunctionsIgnored << " functions " + << format("(%.2lf%%)", IgnoredPercent) + << " because of CFI inconsistencies\n"; + + // Errors in the input are expected from two sources: + // - compilers emitting incorrect CFIs. This happens more frequently with + // older compiler versions, but it should not account for a large + // percentage. + // - input binary is using synchronous unwind tables. This means that after + // call sites, the unwind CFIs are dropped: the pass sees missing + // .cfi_negate_ra_state from autiasp instructions. If this is the case, a + // larger percentage of functions will be ignored. + // + // This is why the 10% threshold was chosen: we should not warn about + // synchronous unwind tables if only a few % are ignored. + if (IgnoredPercent >= 10.0) + BC.outs() << "BOLT-WARNING: PointerAuthCFIAnalyzer only supports " + "asynchronous unwind tables. For C compilers, see " + "-fasynchronous-unwind-tables.\n"; + + return Error::success(); +} + +} // end namespace bolt +} // end namespace llvm diff --git a/bolt/lib/Passes/PointerAuthCFIFixup.cpp b/bolt/lib/Passes/PointerAuthCFIFixup.cpp new file mode 100644 index 000000000000..56086da49b0d --- /dev/null +++ b/bolt/lib/Passes/PointerAuthCFIFixup.cpp @@ -0,0 +1,268 @@ +//===- bolt/Passes/PointerAuthCFIFixup.cpp --------------------------------===// +// +// 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 PointerAuthCFIFixup class. It inserts +// OpNegateRAState CFIs to places where the state of two consecutive +// instructions are different. +// +//===----------------------------------------------------------------------===// +#include "bolt/Passes/PointerAuthCFIFixup.h" +#include "bolt/Core/BinaryFunction.h" +#include "bolt/Core/ParallelUtilities.h" +#include + +using namespace llvm; + +namespace llvm { +namespace bolt { + +static bool PassFailed = false; + +void PointerAuthCFIFixup::runOnFunction(BinaryFunction &BF) { + if (PassFailed) + return; + + BinaryContext &BC = BF.getBinaryContext(); + + if (BF.getState() == BinaryFunction::State::Empty) + return; + + if (BF.getState() != BinaryFunction::State::CFG && + BF.getState() != BinaryFunction::State::CFG_Finalized) { + BC.outs() << "BOLT-INFO: no CFG for " << BF.getPrintName() + << " in PointerAuthCFIFixup\n"; + return; + } + + inferUnknownStates(BF); + + for (FunctionFragment &FF : BF.getLayout().fragments()) { + coverFunctionFragmentStart(BF, FF); + bool FirstIter = true; + bool PrevRAState = false; + // As this pass runs after function splitting, we should only check + // consecutive instructions inside FunctionFragments. + for (BinaryBasicBlock *BB : FF) { + for (auto It = BB->begin(); It != BB->end(); ++It) { + MCInst &Inst = *It; + if (BC.MIB->isCFI(Inst)) + continue; + std::optional RAState = BC.MIB->getRAState(Inst); + if (!RAState.has_value()) { + BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates " + << " in function " << BF.getPrintName() << "\n"; + PassFailed = true; + return; + } + if (!FirstIter) { + // Consecutive instructions with different RAState means we need to + // add a OpNegateRAState. + if (*RAState != PrevRAState) + It = BF.addCFIInstruction( + BB, It, MCCFIInstruction::createNegateRAState(nullptr)); + } else { + FirstIter = false; + } + PrevRAState = *RAState; + } + } + } +} + +void PointerAuthCFIFixup::inferUnknownStates(BinaryFunction &BF) { + BinaryContext &BC = BF.getBinaryContext(); + + // Fill in missing RAStates in simple cases (inside BBs). + for (BinaryBasicBlock &BB : BF) { + fillUnknownStateInBB(BC, BB); + } + // BasicBlocks which are made entirely of "new instructions" (instructions + // without RAState annotation) are stubs, and do not have correct unwind info. + // We should iterate in layout order and fill them based on previous known + // RAState. + fillUnknownStubs(BF); +} + +void PointerAuthCFIFixup::coverFunctionFragmentStart(BinaryFunction &BF, + FunctionFragment &FF) { + BinaryContext &BC = BF.getBinaryContext(); + if (FF.empty()) + return; + // Find the first BB in the FF which has Instructions. + // BOLT can generate empty BBs at function splitting which are only used as + // target labels. We should add the negate-ra-state CFI to the first + // non-empty BB. + auto *FirstNonEmpty = + std::find_if(FF.begin(), FF.end(), [](BinaryBasicBlock *BB) { + // getFirstNonPseudo returns BB.end() if it does not find any + // Instructions. + return BB->getFirstNonPseudo() != BB->end(); + }); + // If a function is already split in the input, the first FF can also start + // with Signed state. This covers that scenario as well. + auto II = (*FirstNonEmpty)->getFirstNonPseudo(); + std::optional RAState = BC.MIB->getRAState(*II); + if (!RAState.has_value()) { + BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates " + << " in function " << BF.getPrintName() << "\n"; + PassFailed = true; + return; + } + if (*RAState) + BF.addCFIInstruction(*FirstNonEmpty, II, + MCCFIInstruction::createNegateRAState(nullptr)); +} + +std::optional +PointerAuthCFIFixup::getFirstKnownRAState(BinaryContext &BC, + BinaryBasicBlock &BB) { + for (const MCInst &Inst : BB) { + if (BC.MIB->isCFI(Inst)) + continue; + std::optional RAState = BC.MIB->getRAState(Inst); + if (RAState.has_value()) + return RAState; + } + return std::nullopt; +} + +bool PointerAuthCFIFixup::isUnknownBlock(BinaryContext &BC, + BinaryBasicBlock &BB) { + std::optional FirstRAState = getFirstKnownRAState(BC, BB); + return !FirstRAState.has_value(); +} + +void PointerAuthCFIFixup::fillUnknownStateInBB(BinaryContext &BC, + BinaryBasicBlock &BB) { + + auto First = BB.getFirstNonPseudo(); + if (First == BB.end()) + return; + // If the first instruction has unknown RAState, we should copy the first + // known RAState. + std::optional RAState = BC.MIB->getRAState(*First); + if (!RAState.has_value()) { + std::optional FirstRAState = getFirstKnownRAState(BC, BB); + if (!FirstRAState.has_value()) + // We fill unknown BBs later. + return; + + BC.MIB->setRAState(*First, *FirstRAState); + } + + // At this point we know the RAState of the first instruction, + // so we can propagate the RAStates to all subsequent unknown instructions. + MCInst Prev = *First; + for (auto It = First + 1; It != BB.end(); ++It) { + MCInst &Inst = *It; + if (BC.MIB->isCFI(Inst)) + continue; + + // No need to check for nullopt: we only entered this loop after the first + // instruction had its RAState set, and RAState is always set for the + // previous instruction in the previous iteration of the loop. + std::optional PrevRAState = BC.MIB->getRAState(Prev); + + std::optional RAState = BC.MIB->getRAState(Inst); + if (!RAState.has_value()) { + if (BC.MIB->isPSignOnLR(Prev)) + PrevRAState = true; + else if (BC.MIB->isPAuthOnLR(Prev)) + PrevRAState = false; + BC.MIB->setRAState(Inst, *PrevRAState); + } + Prev = Inst; + } +} + +void PointerAuthCFIFixup::markUnknownBlock(BinaryContext &BC, + BinaryBasicBlock &BB, bool State) { + // If we call this when an Instruction has either kRASigned or kRAUnsigned + // annotation, setRASigned or setRAUnsigned would fail. + assert(isUnknownBlock(BC, BB) && + "markUnknownBlock should only be called on unknown blocks"); + for (MCInst &Inst : BB) { + if (BC.MIB->isCFI(Inst)) + continue; + BC.MIB->setRAState(Inst, State); + } +} + +void PointerAuthCFIFixup::fillUnknownStubs(BinaryFunction &BF) { + BinaryContext &BC = BF.getBinaryContext(); + bool FirstIter = true; + MCInst PrevInst; + for (FunctionFragment &FF : BF.getLayout().fragments()) { + for (BinaryBasicBlock *BB : FF) { + if (FirstIter) { + FirstIter = false; + if (isUnknownBlock(BC, *BB)) + // If the first BasicBlock is unknown, the function's entry RAState + // should be used. + markUnknownBlock(BC, *BB, BF.getInitialRAState()); + } else if (isUnknownBlock(BC, *BB)) { + // As explained in issue #160989, the unwind info is incorrect for + // stubs. Indicating the correct RAState without the rest of the unwind + // info being correct is not useful. Instead, we copy the RAState from + // the previous instruction. + std::optional PrevRAState = BC.MIB->getRAState(PrevInst); + if (!PrevRAState.has_value()) { + // No non-cfi instruction encountered in the function yet. + // This means the RAState is the same as at the function entry. + markUnknownBlock(BC, *BB, BF.getInitialRAState()); + continue; + } + + if (BC.MIB->isPSignOnLR(PrevInst)) + PrevRAState = true; + else if (BC.MIB->isPAuthOnLR(PrevInst)) + PrevRAState = false; + markUnknownBlock(BC, *BB, *PrevRAState); + } + // This function iterates on BasicBlocks, so the PrevInst has to be + // updated to the last instruction of the current BasicBlock. If the + // BasicBlock is empty, or only has PseudoInstructions, PrevInst will not + // be updated. + auto Last = BB->getLastNonPseudo(); + if (Last != BB->rend()) + PrevInst = *Last; + } + } +} + +Error PointerAuthCFIFixup::runOnFunctions(BinaryContext &BC) { + std::atomic FunctionsModified{0}; + ParallelUtilities::WorkFuncTy WorkFun = [&](BinaryFunction &BF) { + FunctionsModified++; + runOnFunction(BF); + }; + + ParallelUtilities::PredicateTy SkipPredicate = [&](const BinaryFunction &BF) { + // We can skip functions which did not include negate-ra-state CFIs. This + // includes code using pac-ret hardening as well, if the binary is + // compiled with `-fno-exceptions -fno-unwind-tables + // -fno-asynchronous-unwind-tables` + return !BF.containedNegateRAState() || BF.isIgnored(); + }; + + ParallelUtilities::runOnEachFunction( + BC, ParallelUtilities::SchedulingPolicy::SP_INST_LINEAR, WorkFun, + SkipPredicate, "PointerAuthCFIFixup"); + + BC.outs() << "BOLT-INFO: rewritten pac-ret DWARF info in " + << FunctionsModified << " out of " << BC.getBinaryFunctions().size() + << " functions " + << format("(%.2lf%%).\n", (100.0 * FunctionsModified) / + BC.getBinaryFunctions().size()); + if (PassFailed) + return createFatalBOLTError(""); + return Error::success(); +} + +} // end namespace bolt +} // end namespace llvm diff --git a/bolt/lib/Passes/VeneerElimination.cpp b/bolt/lib/Passes/VeneerElimination.cpp index 99d0ffeca8cc..c0a6bcfbdd75 100644 --- a/bolt/lib/Passes/VeneerElimination.cpp +++ b/bolt/lib/Passes/VeneerElimination.cpp @@ -29,10 +29,6 @@ static llvm::cl::opt namespace llvm { namespace bolt { -static bool isPossibleVeneer(const BinaryFunction &BF) { - return BF.isAArch64Veneer() || BF.getOneName().starts_with("__AArch64"); -} - Error VeneerElimination::runOnFunctions(BinaryContext &BC) { if (!opts::EliminateVeneers || !BC.isAArch64()) return Error::success(); @@ -40,7 +36,7 @@ Error VeneerElimination::runOnFunctions(BinaryContext &BC) { std::unordered_map VeneerDestinations; uint64_t NumEliminatedVeneers = 0; for (BinaryFunction &BF : llvm::make_second_range(BC.getBinaryFunctions())) { - if (!isPossibleVeneer(BF)) + if (!BF.isPossibleVeneer()) continue; if (BF.isIgnored()) diff --git a/bolt/lib/Rewrite/BinaryPassManager.cpp b/bolt/lib/Rewrite/BinaryPassManager.cpp index 1a0f6d75d63e..85f23dceefe2 100644 --- a/bolt/lib/Rewrite/BinaryPassManager.cpp +++ b/bolt/lib/Rewrite/BinaryPassManager.cpp @@ -19,15 +19,15 @@ #include "bolt/Passes/IdenticalCodeFolding.h" #include "bolt/Passes/IndirectCallPromotion.h" #include "bolt/Passes/Inliner.h" -#include "bolt/Passes/InsertNegateRAStatePass.h" #include "bolt/Passes/Instrumentation.h" #include "bolt/Passes/JTFootprintReduction.h" #include "bolt/Passes/LongJmp.h" #include "bolt/Passes/LoopInversionPass.h" #include "bolt/Passes/MCF.h" -#include "bolt/Passes/MarkRAStates.h" #include "bolt/Passes/PLTCall.h" #include "bolt/Passes/PatchEntries.h" +#include "bolt/Passes/PointerAuthCFIAnalyzer.h" +#include "bolt/Passes/PointerAuthCFIFixup.h" #include "bolt/Passes/ProfileQualityStats.h" #include "bolt/Passes/RegReAssign.h" #include "bolt/Passes/ReorderData.h" @@ -134,6 +134,15 @@ static cl::opt PrintAArch64Relaxation( cl::desc("print functions after ADR/LDR Relaxation pass"), cl::Hidden, cl::cat(BoltOptCategory)); +cl::opt PrintPAuthCFIAnalyzer( + "print-pointer-auth-cfi-analyzer", + cl::desc("print functions after PointerAuthCFIAnalyzer pass"), cl::Hidden, + cl::cat(BoltOptCategory)); +static cl::opt PrintPAuthCFIFixup( + "print-pointer-auth-cfi-fixup", + cl::desc("print functions after PointerAuthCFIFixup pass"), cl::Hidden, + cl::cat(BoltOptCategory)); + static cl::opt PrintLongJmp("print-longjmp", cl::desc("print functions after longjmp pass"), cl::Hidden, @@ -362,7 +371,8 @@ Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) { BinaryFunctionPassManager Manager(BC); if (BC.isAArch64()) - Manager.registerPass(std::make_unique()); + Manager.registerPass( + std::make_unique(PrintPAuthCFIAnalyzer)); Manager.registerPass( std::make_unique(PrintEstimateEdgeCounts)); @@ -524,7 +534,8 @@ Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) { // relocations out of range and crash during linking. Manager.registerPass(std::make_unique(PrintLongJmp)); - Manager.registerPass(std::make_unique()); + Manager.registerPass( + std::make_unique(PrintPAuthCFIFixup)); } // This pass should always run last.* diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp index 8a5bbe28e5f6..2ef03d283366 100644 --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -80,6 +80,7 @@ namespace opts { extern cl::list HotTextMoveSections; extern cl::opt Hugify; extern cl::opt Instrument; +extern cl::opt InstrumentationSleepTime; extern cl::opt KeepNops; extern cl::opt Lite; extern cl::list PrintOnly; @@ -294,6 +295,28 @@ cl::bits GadgetScannersToRun( clEnumValN(GS_ALL, "all", "All implemented scanners")), cl::ZeroOrMore, cl::CommaSeparated, cl::cat(BinaryAnalysisCategory)); +// Primary targets for hooking runtime library initialization hooking +// with fallback to next item in case if current item is not available +// in the input binary. +enum RuntimeLibInitHookTarget : char { + RLIH_ENTRY_POINT = 0, /// Use ELF Header Entry Point + RLIH_INIT = 1, /// Use ELF DT_INIT entry + RLIH_INIT_ARRAY = 2, /// Use ELF .init_array entry +}; + +cl::opt RuntimeLibInitHook( + "runtime-lib-init-hook", + cl::desc("Primary target for hooking runtime library initialization, used " + "in fallback order of availabiliy in input binary (entry_point -> " + "init -> init_array) (default: entry_point)"), + cl::Hidden, cl::init(RLIH_ENTRY_POINT), + cl::values(clEnumValN(RLIH_ENTRY_POINT, "entry_point", + "use ELF Header Entry Point"), + clEnumValN(RLIH_INIT, "init", "use ELF DT_INIT entry"), + clEnumValN(RLIH_INIT_ARRAY, "init_array", + "use ELF .init_array entry")), + cl::ZeroOrMore, cl::cat(BoltOptCategory)); + } // namespace opts // FIXME: implement a better way to mark sections for replacement. @@ -741,9 +764,12 @@ Error RewriteInstance::run() { adjustCommandLineOptions(); discoverFileObjects(); - if (opts::Instrument && !BC->IsStaticExecutable) + if (opts::Instrument && !BC->IsStaticExecutable) { + if (Error E = discoverRtInitAddress()) + return E; if (Error E = discoverRtFiniAddress()) return E; + } preprocessProfileData(); @@ -785,8 +811,12 @@ Error RewriteInstance::run() { updateMetadata(); - if (opts::Instrument && !BC->IsStaticExecutable) - updateRtFiniReloc(); + if (opts::Instrument && !BC->IsStaticExecutable) { + if (Error E = updateRtInitReloc()) + return E; + if (Error E = updateRtFiniReloc()) + return E; + } if (opts::OutputFilename == "/dev/null") { BC->outs() << "BOLT-INFO: skipping writing final binary to disk\n"; @@ -1411,6 +1441,65 @@ void RewriteInstance::discoverBOLTReserved() { NextAvailableAddress = BC->BOLTReserved.start(); } +Error RewriteInstance::discoverRtInitAddress() { + if (BC->HasInterpHeader && opts::RuntimeLibInitHook == opts::RLIH_ENTRY_POINT) + return Error::success(); + + // Use DT_INIT if it's available. + if (BC->InitAddress && opts::RuntimeLibInitHook <= opts::RLIH_INIT) { + BC->StartFunctionAddress = BC->InitAddress; + return Error::success(); + } + + if (!BC->InitArrayAddress || !BC->InitArraySize) { + return createStringError(std::errc::not_supported, + "Instrumentation of shared library needs either " + "DT_INIT or DT_INIT_ARRAY"); + } + + if (*BC->InitArraySize < BC->AsmInfo->getCodePointerSize()) { + return createStringError(std::errc::not_supported, + "Need at least 1 DT_INIT_ARRAY slot"); + } + + ErrorOr InitArraySection = + BC->getSectionForAddress(*BC->InitArrayAddress); + if (auto EC = InitArraySection.getError()) + return errorCodeToError(EC); + + if (InitArraySection->getAddress() != *BC->InitArrayAddress) { + return createStringError(std::errc::not_supported, + "Inconsistent address of .init_array section"); + } + + if (const Relocation *Reloc = InitArraySection->getDynamicRelocationAt(0)) { + if (Reloc->isRelative()) { + BC->StartFunctionAddress = Reloc->Addend; + } else { + MCSymbol *Sym = Reloc->Symbol; + if (!Sym) + return createStringError( + std::errc::not_supported, + "Failed to locate symbol for 0 entry of .init_array"); + const BinaryFunction *BF = BC->getFunctionForSymbol(Sym); + if (!BF) + return createStringError( + std::errc::not_supported, + "Failed to locate binary function for 0 entry of .init_array"); + BC->StartFunctionAddress = BF->getAddress() + Reloc->Addend; + } + return Error::success(); + } + + if (const Relocation *Reloc = InitArraySection->getRelocationAt(0)) { + BC->StartFunctionAddress = Reloc->Value; + return Error::success(); + } + + return createStringError(std::errc::not_supported, + "No relocation for first DT_INIT_ARRAY slot"); +} + Error RewriteInstance::discoverRtFiniAddress() { // Use DT_FINI if it's available. if (BC->FiniAddress) { @@ -1419,6 +1508,9 @@ Error RewriteInstance::discoverRtFiniAddress() { } if (!BC->FiniArrayAddress || !BC->FiniArraySize) { + // Missing fini hooks are allowed when instrumentation-sleep-time is in use. + if (opts::InstrumentationSleepTime > 0) + return Error::success(); return createStringError( std::errc::not_supported, "Instrumentation needs either DT_FINI or DT_FINI_ARRAY"); @@ -1434,6 +1526,11 @@ Error RewriteInstance::discoverRtFiniAddress() { if (auto EC = FiniArraySection.getError()) return errorCodeToError(EC); + if (FiniArraySection->getAddress() != *BC->FiniArrayAddress) { + return createStringError(std::errc::not_supported, + "Inconsistent address of .fini_array section"); + } + if (const Relocation *Reloc = FiniArraySection->getDynamicRelocationAt(0)) { BC->FiniFunctionAddress = Reloc->Addend; return Error::success(); @@ -1448,26 +1545,99 @@ Error RewriteInstance::discoverRtFiniAddress() { "No relocation for first DT_FINI_ARRAY slot"); } -void RewriteInstance::updateRtFiniReloc() { +Error RewriteInstance::updateRtInitReloc() { + if (BC->HasInterpHeader && opts::RuntimeLibInitHook == opts::RLIH_ENTRY_POINT) + return Error::success(); + + // Updating DT_INIT is handled by patchELFDynamic. + if (BC->InitAddress && opts::RuntimeLibInitHook <= opts::RLIH_INIT) + return Error::success(); + + const RuntimeLibrary *RT = BC->getRuntimeLibrary(); + if (!RT || !RT->getRuntimeStartAddress()) + return Error::success(); + + if (!BC->InitArrayAddress) + return Error::success(); + + if (!BC->InitArrayAddress || !BC->InitArraySize) + return createStringError(std::errc::not_supported, + "inconsistent .init_array state"); + + ErrorOr InitArraySection = + BC->getSectionForAddress(*BC->InitArrayAddress); + if (!InitArraySection) + return createStringError(std::errc::not_supported, ".init_array removed"); + + if (std::optional Reloc = + InitArraySection->takeDynamicRelocationAt(0)) { + if (Reloc->isRelative()) { + if (Reloc->Addend != BC->StartFunctionAddress) + return createStringError(std::errc::not_supported, + "inconsistent .init_array dynamic relocation"); + Reloc->Addend = RT->getRuntimeStartAddress(); + InitArraySection->addDynamicRelocation(*Reloc); + } else { + MCSymbol *Sym = Reloc->Symbol; + if (!Sym) + return createStringError( + std::errc::not_supported, + "Failed to locate symbol for 0 entry of .init_array"); + const BinaryFunction *BF = BC->getFunctionForSymbol(Sym); + if (!BF) + return createStringError( + std::errc::not_supported, + "Failed to locate binary function for 0 entry of .init_array"); + if (BF->getAddress() + Reloc->Addend != BC->StartFunctionAddress) + return createStringError(std::errc::not_supported, + "inconsistent .init_array dynamic relocation"); + InitArraySection->addDynamicRelocation(Relocation{ + /*Offset*/ 0, /*Symbol*/ nullptr, /*Type*/ Relocation::getAbs64(), + /*Addend*/ RT->getRuntimeStartAddress(), /*Value*/ 0}); + } + } + // Update the static relocation by adding a pending relocation which will get + // patched when flushPendingRelocations is called in rewriteFile. Note that + // flushPendingRelocations will calculate the value to patch as + // "Symbol + Addend". Since we don't have a symbol, just set the addend to the + // desired value. + InitArraySection->addPendingRelocation(Relocation{ + /*Offset*/ 0, /*Symbol*/ nullptr, /*Type*/ Relocation::getAbs64(), + /*Addend*/ RT->getRuntimeStartAddress(), /*Value*/ 0}); + BC->outs() + << "BOLT-INFO: runtime library initialization was hooked via .init_array " + "entry, set to 0x" + << Twine::utohexstr(RT->getRuntimeStartAddress()) << "\n"; + return Error::success(); +} + +Error RewriteInstance::updateRtFiniReloc() { // Updating DT_FINI is handled by patchELFDynamic. if (BC->FiniAddress) - return; + return Error::success(); const RuntimeLibrary *RT = BC->getRuntimeLibrary(); if (!RT || !RT->getRuntimeFiniAddress()) - return; + return Error::success(); - assert(BC->FiniArrayAddress && BC->FiniArraySize && - "inconsistent .fini_array state"); + if (!BC->FiniArrayAddress || !BC->FiniArraySize) { + // Missing fini hooks are allowed when instrumentation-sleep-time is in use. + if (opts::InstrumentationSleepTime > 0) + return Error::success(); + return createStringError(std::errc::not_supported, + "inconsistent .fini_array state"); + } ErrorOr FiniArraySection = BC->getSectionForAddress(*BC->FiniArrayAddress); - assert(FiniArraySection && ".fini_array removed"); + if (!FiniArraySection) + return createStringError(std::errc::not_supported, ".fini_array removed"); if (std::optional Reloc = FiniArraySection->takeDynamicRelocationAt(0)) { - assert(Reloc->Addend == BC->FiniFunctionAddress && - "inconsistent .fini_array dynamic relocation"); + if (Reloc->Addend != BC->FiniFunctionAddress) + return createStringError(std::errc::not_supported, + "inconsistent .fini_array dynamic relocation"); Reloc->Addend = RT->getRuntimeFiniAddress(); FiniArraySection->addDynamicRelocation(*Reloc); } @@ -1480,6 +1650,10 @@ void RewriteInstance::updateRtFiniReloc() { FiniArraySection->addPendingRelocation(Relocation{ /*Offset*/ 0, /*Symbol*/ nullptr, /*Type*/ Relocation::getAbs64(), /*Addend*/ RT->getRuntimeFiniAddress(), /*Value*/ 0}); + BC->outs() << "BOLT-INFO: runtime library finalization was hooked via " + ".fini_array entry, set to 0x" + << Twine::utohexstr(RT->getRuntimeFiniAddress()) << "\n"; + return Error::success(); } void RewriteInstance::registerFragments() { @@ -2178,6 +2352,14 @@ void RewriteInstance::adjustCommandLineOptions() { exit(1); } + if (opts::Instrument && opts::RuntimeLibInitHook == opts::RLIH_ENTRY_POINT && + !BC->HasInterpHeader) { + BC->errs() + << "BOLT-WARNING: adjusted runtime-lib-init-hook to 'init' due to " + "absence of INTERP header\n"; + opts::RuntimeLibInitHook = opts::RLIH_INIT; + } + if (opts::HotText && opts::HotTextMoveSections.getNumOccurrences() == 0) { opts::HotTextMoveSections.addValue(".stub"); opts::HotTextMoveSections.addValue(".mover"); @@ -3192,6 +3374,11 @@ void RewriteInstance::selectFunctionsToProcess() { if (mustSkip(Function)) return false; + // Include veneer functions as we want to replace veneer calls with direct + // ones. + if (Function.isPossibleVeneer()) + return true; + // If the list is not empty, only process functions from the list. if (!opts::ForceFunctionNames.empty() || !ForceFunctionsNR.empty()) { // Regex check (-funcs and -funcs-file options). @@ -3507,6 +3694,7 @@ void RewriteInstance::disassembleFunctions() { if (!shouldDisassemble(Function)) continue; + Function.validateInternalBranches(); Function.postProcessEntryPoints(); Function.postProcessJumpTables(); } @@ -4849,9 +5037,14 @@ void RewriteInstance::patchELFSectionHeaderTable(ELFObjectFile *File) { ELFEhdrTy NewEhdr = Obj.getHeader(); if (BC->HasRelocations) { - if (RuntimeLibrary *RtLibrary = BC->getRuntimeLibrary()) + RuntimeLibrary *RtLibrary = BC->getRuntimeLibrary(); + if (RtLibrary && opts::RuntimeLibInitHook == opts::RLIH_ENTRY_POINT) { NewEhdr.e_entry = RtLibrary->getRuntimeStartAddress(); - else + BC->outs() + << "BOLT-INFO: runtime library initialization was hooked via ELF " + "Header Entry Point, set to 0x" + << Twine::utohexstr(NewEhdr.e_entry) << "\n"; + } else NewEhdr.e_entry = getNewFunctionAddress(NewEhdr.e_entry); assert((NewEhdr.e_entry || !Obj.getHeader().e_entry) && "cannot find new address for entry point"); @@ -5692,14 +5885,23 @@ void RewriteInstance::patchELFDynamic(ELFObjectFile *File) { } RuntimeLibrary *RtLibrary = BC->getRuntimeLibrary(); if (RtLibrary && Dyn.getTag() == ELF::DT_FINI) { - if (uint64_t Addr = RtLibrary->getRuntimeFiniAddress()) + if (uint64_t Addr = RtLibrary->getRuntimeFiniAddress()) { NewDE.d_un.d_ptr = Addr; + BC->outs() + << "BOLT-INFO: runtime library finalization was hooked via " + "DT_FINI, set to 0x" + << Twine::utohexstr(Addr) << "\n"; + } } - if (RtLibrary && Dyn.getTag() == ELF::DT_INIT && !BC->HasInterpHeader) { + if (RtLibrary && Dyn.getTag() == ELF::DT_INIT && + (!BC->HasInterpHeader || + opts::RuntimeLibInitHook == opts::RLIH_INIT)) { if (auto Addr = RtLibrary->getRuntimeStartAddress()) { - LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Set DT_INIT to 0x" - << Twine::utohexstr(Addr) << '\n'); NewDE.d_un.d_ptr = Addr; + BC->outs() + << "BOLT-INFO: runtime library initialization was hooked via " + "DT_INIT, set to 0x" + << Twine::utohexstr(Addr) << "\n"; } } break; @@ -5767,10 +5969,13 @@ Error RewriteInstance::readELFDynamic(ELFObjectFile *File) { for (const Elf_Dyn &Dyn : DynamicEntries) { switch (Dyn.d_tag) { case ELF::DT_INIT: - if (!BC->HasInterpHeader) { - LLVM_DEBUG(dbgs() << "BOLT-DEBUG: Set start function address\n"); - BC->StartFunctionAddress = Dyn.getPtr(); - } + BC->InitAddress = Dyn.getPtr(); + break; + case ELF::DT_INIT_ARRAY: + BC->InitArrayAddress = Dyn.getPtr(); + break; + case ELF::DT_INIT_ARRAYSZ: + BC->InitArraySize = Dyn.getPtr(); break; case ELF::DT_FINI: BC->FiniAddress = Dyn.getPtr(); diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp index f1291f676f1b..031c16df5b15 100644 --- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp +++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp @@ -170,46 +170,48 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { return isLoadFromStack(Inst); } - // We look for instructions that load from stack or make stack pointer - // adjustment, and assume the basic block is an epilogue if and only if - // such instructions are present and also immediately precede the branch - // instruction that ends the basic block. + // We look for instruction that saves LR to or restores LR from stack. + // + // If we ever see an LR save to stack, we assume this block is not an + // epilogue. + // + // If there is no LR save in the block and we see an LR restore from + // stack, we assume it is an epilogue. + // + // If neither is seen, we assume it is not an epilogue. + // + // This is not meant to accurately recognize epilogue in all possible + // cases, but to have BOLT be conservative on treating basic block as + // epilogue and then turning indirect branch with unknown control flow + // to tail call. bool isEpilogue(const BinaryBasicBlock &BB) const override { if (BB.succ_size()) return false; - bool SeenLoadFromStack = false; - bool SeenStackPointerAdjustment = false; - for (const MCInst &Instr : BB) { + bool SeenLRRestoreFromStack = false; + for (auto It = BB.rbegin(); It != BB.rend(); ++It) { + const MCInst &Instr = *It; // Skip CFI pseudo instruction. if (isCFI(Instr)) continue; - - bool IsPop = isPop(Instr); - // A load from stack instruction could do SP adjustment in pre-index or - // post-index form, which we can skip to check for epilogue recognition - // purpose. - bool IsSPAdj = (isADD(Instr) || isMOVW(Instr)) && - Instr.getOperand(0).isReg() && - Instr.getOperand(0).getReg() == AArch64::SP; - SeenLoadFromStack |= IsPop; - SeenStackPointerAdjustment |= IsSPAdj; - - if (!SeenLoadFromStack && !SeenStackPointerAdjustment) - continue; - if (IsPop || IsSPAdj || isPAuthOnLR(Instr)) - continue; if (isReturn(Instr)) return true; - if (isBranch(Instr)) - break; - // Any previously seen load from stack or stack adjustment instruction - // is definitely not part of epilogue code sequence, so reset these two. - SeenLoadFromStack = false; - SeenStackPointerAdjustment = false; + if (isStoreToStack(Instr)) { + for (const MCOperand &Operand : useOperands(Instr)) { + if (Operand.isReg() && Operand.getReg() == AArch64::LR) { + return false; + } + } + } else if (isLoadFromStack(Instr)) { + for (const MCOperand &Operand : defOperands(Instr)) { + if (Operand.isReg() && Operand.getReg() == AArch64::LR) { + SeenLRRestoreFromStack = true; + } + } + } } - return SeenLoadFromStack || SeenStackPointerAdjustment; + return SeenLRRestoreFromStack; } void createCall(MCInst &Inst, const MCSymbol *Target, @@ -1862,14 +1864,12 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { } bool isNoop(const MCInst &Inst) const override { - return Inst.getOpcode() == AArch64::HINT && - Inst.getOperand(0).getImm() == 0; + return Inst.getOpcode() == AArch64::NOP; } void createNoop(MCInst &Inst) const override { - Inst.setOpcode(AArch64::HINT); + Inst.setOpcode(AArch64::NOP); Inst.clear(); - Inst.addOperand(MCOperand::createImm(0)); } bool isTrap(const MCInst &Inst) const override { @@ -2800,6 +2800,89 @@ class AArch64MCPlusBuilder : public MCPlusBuilder { Inst.getOpcode() == AArch64::PACIBSP; } + void updateBTIVariant(MCInst &Inst, bool CallTarget, + bool JumpTarget) const override { + assert(Inst.getOpcode() == AArch64::HINT && "Not a BTI instruction."); + unsigned HintNum = getBTIHintNum(CallTarget, JumpTarget); + Inst.clear(); + Inst.addOperand(MCOperand::createImm(HintNum)); + } + + bool isCallCoveredByBTI(MCInst &Call, MCInst &Pad) const override { + assert((isIndirectCall(Call) || isIndirectBranch(Call)) && + "Not an indirect call or branch."); + + // A BLR can be accepted by a BTI c. + if (isIndirectCall(Call)) + return isBTILandingPad(Pad, true, false) || + isBTILandingPad(Pad, true, true); + + // A BR can be accepted by a BTI j or BTI c (and BTI jc) IF the operand is + // x16 or x17. If the operand is not x16 or x17, it can be accepted by a BTI + // j or BTI jc (and not BTI c). + if (isIndirectBranch(Call)) { + assert(Call.getNumOperands() == 1 && + "Indirect branch needs to have 1 operand."); + assert(Call.getOperand(0).isReg() && + "Indirect branch does not have a register operand."); + MCPhysReg Reg = Call.getOperand(0).getReg(); + if (Reg == AArch64::X16 || Reg == AArch64::X17) + return isBTILandingPad(Pad, true, false) || + isBTILandingPad(Pad, false, true) || + isBTILandingPad(Pad, true, true); + return isBTILandingPad(Pad, false, true) || + isBTILandingPad(Pad, true, true); + } + return false; + } + + void insertBTI(BinaryBasicBlock &BB, MCInst &Call) const override { + auto II = BB.getFirstNonPseudo(); + // Only check the first instruction for non-empty BasicBlocks + bool Empty = (II == BB.end()); + if (!Empty && isCallCoveredByBTI(Call, *II)) + return; + // A BLR can be accepted by a BTI c. + if (isIndirectCall(Call)) { + // if we have a BTI j at the start, extend it to a BTI jc, + // otherwise insert a new BTI c. + if (!Empty && isBTILandingPad(*II, false, true)) { + updateBTIVariant(*II, true, true); + } else { + MCInst BTIInst; + createBTI(BTIInst, true, false); + BB.insertInstruction(II, BTIInst); + } + } + + // A BR can be accepted by a BTI j or BTI c (and BTI jc) IF the operand is + // x16 or x17. If the operand is not x16 or x17, it can be accepted by a + // BTI j or BTI jc (and not BTI c). + if (isIndirectBranch(Call)) { + assert(Call.getNumOperands() == 1 && + "Indirect branch needs to have 1 operand."); + assert(Call.getOperand(0).isReg() && + "Indirect branch does not have a register operand."); + MCPhysReg Reg = Call.getOperand(0).getReg(); + if (Reg == AArch64::X16 || Reg == AArch64::X17) { + // Add a new BTI c + MCInst BTIInst; + createBTI(BTIInst, true, false); + BB.insertInstruction(II, BTIInst); + } else { + // If BB starts with a BTI c, extend it to BTI jc, + // otherwise insert a new BTI j. + if (!Empty && isBTILandingPad(*II, true, false)) { + updateBTIVariant(*II, true, true); + } else { + MCInst BTIInst; + createBTI(BTIInst, false, true); + BB.insertInstruction(II, BTIInst); + } + } + } + } + InstructionListType materializeAddress(const MCSymbol *Target, MCContext *Ctx, MCPhysReg RegName, int64_t Addend = 0) const override { diff --git a/bolt/test/AArch64/constant-island-alignment.s b/bolt/test/AArch64/constant-island-alignment.s index 99fe7333e2db..5462dcb08f82 100644 --- a/bolt/test/AArch64/constant-island-alignment.s +++ b/bolt/test/AArch64/constant-island-alignment.s @@ -53,6 +53,7 @@ _start: blr x0 mov x0, #1 ret +.size _start,.-_start nop # CHECK: {{0|8}} <$d>: .Lci: diff --git a/bolt/test/AArch64/epilogue-determination.s b/bolt/test/AArch64/epilogue-determination.s index 437d8149c0d6..47f9de8fdcd0 100644 --- a/bolt/test/AArch64/epilogue-determination.s +++ b/bolt/test/AArch64/epilogue-determination.s @@ -1,5 +1,6 @@ # Test that we will not incorrectly take the first basic block in function -# `_foo` as epilogue due to the first load from stack instruction. +# `_foo` or the second basic block in function `_goo` as epilogue, and will +# recognize epilogues in the other cases. # RUN: %clang %cflags %s -o %t.so -Wl,-q # RUN: llvm-bolt %t.so -o %t.bolt --print-cfg | FileCheck %s @@ -13,8 +14,9 @@ _foo: ldrsw x9, [x10, x9, lsl #2] add x10, x10, x9 br x10 -# CHECK-NOT: x10 # TAILCALL -# CHECK: x10 # UNKNOWN CONTROL FLOW +# CHECK-LABEL: _foo +# CHECK: br x10 +# CHECK-SAME: # UNKNOWN CONTROL FLOW mov x0, 0 ret mov x0, 1 @@ -31,13 +33,51 @@ _bar: stp x29, x30, [sp, #-0x10]! mov x29, sp sub sp, sp, #0x10 + tbnz x0, #0x3, _L2 ldr x8, [x29, #0x30] blr x8 +_L2: add sp, sp, #0x10 ldp x29, x30, [sp], #0x10 br x2 -# CHECK-NOT: x2 # UNKNOWN CONTROL FLOW -# CHECK: x2 # TAILCALL +# CHECK-LABEL: _bar +# CHECK: br x2 +# CHECK-SAME: # TAILCALL + + .global _goo + .type _goo, %function +_goo: + ldr w8, [sp] + adr x10, _jmptbl2 + ldrsw x9, [x10, x9, lsl #2] + add x10, x10, x9 + str x30, [sp, #-0x10]! + bl _bar + ldr x30, [sp], #0x10 + mov x1, x0 + mov x0, xzr + br x10 +# CHECK-LABEL: _goo +# CHECK: br x10 +# CHECK-SAME: # UNKNOWN CONTROL FLOW + mov x0, 0 + ret + mov x0, 1 + ret + + .global _faz + .type _faz, %function +_faz: + str x30, [sp, #-0x10]! + tbnz x0, #0x1, _L3 + bl _bar +_L3: + ldr x30, [sp], #0x10 + mov x0, x1 + br x3 +# CHECK-LABEL: _faz +# CHECK: br x3 +# CHECK-SAME: # TAILCALL .global _start .type _start, %function diff --git a/bolt/test/AArch64/hook-fini.s b/bolt/test/AArch64/hook-fini.s index 4f321d463ef3..3bb95f9317b1 100644 --- a/bolt/test/AArch64/hook-fini.s +++ b/bolt/test/AArch64/hook-fini.s @@ -15,13 +15,13 @@ # RUN: %clang %cflags -pie %s -Wl,-q -o %t.exe # RUN: llvm-readelf -d %t.exe | FileCheck --check-prefix=DYN-FINI %s # RUN: llvm-readelf -r %t.exe | FileCheck --check-prefix=RELOC-PIE %s -# RUN: llvm-bolt %t.exe -o %t --instrument +# RUN: llvm-bolt %t.exe -o %t --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-FINI %s # RUN: llvm-readelf -drs %t | FileCheck --check-prefix=CHECK-FINI %s # RUN: %clang %cflags -pie %s -Wl,-q,-fini=0 -o %t-no-fini.exe # RUN: llvm-readelf -d %t-no-fini.exe | FileCheck --check-prefix=DYN-NO-FINI %s # RUN: llvm-readelf -r %t-no-fini.exe | FileCheck --check-prefix=RELOC-PIE %s -# RUN: llvm-bolt %t-no-fini.exe -o %t-no-fini --instrument +# RUN: llvm-bolt %t-no-fini.exe -o %t-no-fini --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-FINI-ARRAY %s # RUN: llvm-readelf -drs %t-no-fini | FileCheck --check-prefix=CHECK-NO-FINI %s # RUN: llvm-readelf -ds -x .fini_array %t-no-fini | FileCheck --check-prefix=CHECK-NO-FINI-RELOC %s @@ -29,7 +29,7 @@ # RUN: %clang %cflags %p/../Inputs/stub.c -fPIC -shared -o %t-stub.so # RUN: %clang %cflags %s -no-pie -Wl,-q,-fini=0 %t-stub.so -o %t-no-pie-no-fini.exe # RUN: llvm-readelf -r %t-no-pie-no-fini.exe | FileCheck --check-prefix=RELOC-NO-PIE %s -# RUN: llvm-bolt %t-no-pie-no-fini.exe -o %t-no-pie-no-fini --instrument +# RUN: llvm-bolt %t-no-pie-no-fini.exe -o %t-no-pie-no-fini --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-FINI-ARRAY %s # RUN: llvm-readelf -ds -x .fini_array %t-no-pie-no-fini | FileCheck --check-prefix=CHECK-NO-PIE-NO-FINI %s ## With fini: dynamic section should contain DT_FINI @@ -46,6 +46,14 @@ ## Without PIE: binary should not have relative relocations # RELOC-NO-PIE-NOT: R_AARCH64_RELATIVE +## Check BOLT output output finalization hook (DT_FINI) +# CHECK-BOLT-RT-FINI: runtime library finalization was hooked via DT_FINI +# CHECK-BOLT-RT-FINI-NOT: runtime library finalization was hooked via .fini_array entry + +## Check BOLT output output finalization hook (.fini_array entry) +# CHECK-BOLT-RT-FINI-ARRAY-NOT: runtime library finalization was hooked via DT_FINI +# CHECK-BOLT-RT-FINI-ARRAY: runtime library finalization was hooked via .fini_array entry + ## Check that DT_FINI is set to __bolt_runtime_fini # CHECK-FINI: Dynamic section at offset {{.*}} contains {{.*}} entries: # CHECK-FINI-DAG: (FINI) 0x[[FINI:[[:xdigit:]]+]] diff --git a/bolt/test/AArch64/hook-init.s b/bolt/test/AArch64/hook-init.s new file mode 100644 index 000000000000..a48328b630fa --- /dev/null +++ b/bolt/test/AArch64/hook-init.s @@ -0,0 +1,221 @@ +## Test the different ways of hooking the init function for instrumentation (via +## entry point, DT_INIT and via DT_INIT_ARRAY). We test the latter for both PIE +## and non-PIE binaries because of the different ways of handling relocations +## (static or dynamic), executable and shared library. +## All tests perform the following steps: +## - Compile and link for the case to be tested +## - Some sanity-checks on the dynamic section and relocations in the binary to +## verify it has the shape we want for testing: +## - INTERP in Program Headers +## - DT_INIT or DT_INIT_ARRAY in dynamic section +## - No relative relocations for non-PIE +## - Instrument (with extra --runtime-lib-init-hook=init/init_array options +## in some cases) +## - Verify generated binary +# REQUIRES: system-linux,bolt-runtime,target=aarch64{{.*}} + +# RUN: %clang %cflags -pie %s -Wl,-q -o %t.exe +# RUN: llvm-readelf -d %t.exe | FileCheck --check-prefix=DYN-INIT %s +# RUN: llvm-readelf -l %t.exe | FileCheck --check-prefix=PH-INTERP %s +# RUN: llvm-readelf -r %t.exe | FileCheck --check-prefix=RELOC-PIE %s +# RUN: llvm-bolt %t.exe -o %t --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-EP %s +# RUN: llvm-readelf -hdrs %t | FileCheck --check-prefix=CHECK-INIT-EP %s +# RUN: llvm-bolt %t.exe -o %t-no-ep --instrument --runtime-lib-init-hook=init | FileCheck --check-prefix=CHECK-BOLT-RT-INIT %s +# RUN: llvm-readelf -hdrs %t-no-ep | FileCheck --check-prefix=CHECK-INIT-NO-EP %s +# RUN: llvm-bolt %t.exe -o %t-no-ep --instrument --runtime-lib-init-hook=init_array | FileCheck --check-prefix=CHECK-BOLT-RT-INIT-ARRAY %s +# RUN: llvm-readelf -hdrs %t-no-ep | FileCheck --check-prefix=CHECK-INIT-ARRAY-NO-EP %s + +# RUN: %clang -shared %cflags -pie %s -Wl,-q -o %t-shared.exe +# RUN: llvm-readelf -d %t-shared.exe | FileCheck --check-prefix=DYN-INIT %s +# RUN: llvm-readelf -l %t-shared.exe | FileCheck --check-prefix=PH-INTERP-SHARED %s +# RUN: llvm-readelf -r %t-shared.exe | FileCheck --check-prefix=RELOC-SHARED-PIE %s +# RUN: llvm-bolt %t-shared.exe -o %t-shared --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-INIT %s +# RUN: llvm-readelf -hdrs %t-shared | FileCheck --check-prefix=CHECK-SHARED-INIT %s + +# RUN: %clang %cflags -pie %s -Wl,-q,-init=0 -o %t-no-init.exe +# RUN: llvm-readelf -d %t-no-init.exe | FileCheck --check-prefix=DYN-NO-INIT %s +# RUN: llvm-readelf -l %t-no-init.exe | FileCheck --check-prefix=PH-INTERP %s +# RUN: llvm-readelf -r %t-no-init.exe | FileCheck --check-prefix=RELOC-PIE %s +# RUN: llvm-bolt %t-no-init.exe -o %t-no-init --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-EP %s +# RUN: llvm-readelf -hdrs %t-no-init | FileCheck --check-prefix=CHECK-NO-INIT-EP %s +# RUN: llvm-bolt %t-no-init.exe -o %t-no-init-no-ep --instrument --runtime-lib-init-hook=init | FileCheck --check-prefix=CHECK-BOLT-RT-INIT-ARRAY %s +# RUN: llvm-readelf -hdrs %t-no-init-no-ep | FileCheck --check-prefix=CHECK-NO-INIT-NO-EP %s + +# RUN: %clang -shared %cflags -pie %s -Wl,-q,-init=0 -o %t-shared-no-init.exe +# RUN: llvm-readelf -d %t-shared-no-init.exe | FileCheck --check-prefix=DYN-NO-INIT %s +# RUN: llvm-readelf -l %t-shared-no-init.exe | FileCheck --check-prefix=PH-INTERP-SHARED %s +# RUN: llvm-readelf -r %t-shared-no-init.exe | FileCheck --check-prefix=RELOC-SHARED-PIE %s +# RUN: llvm-bolt %t-shared-no-init.exe -o %t-shared-no-init --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-INIT-ARRAY %s +# RUN: llvm-readelf -drs %t-shared-no-init | FileCheck --check-prefix=CHECK-SHARED-NO-INIT %s + +## Create a dummy shared library to link against to force creation of the dynamic section. +# RUN: %clang %cflags %p/../Inputs/stub.c -fPIC -shared -o %t-stub.so +# RUN: %clang %cflags %s -no-pie -Wl,-q,-init=0 %t-stub.so -o %t-no-pie-no-init.exe +# RUN: llvm-readelf -r %t-no-pie-no-init.exe | FileCheck --check-prefix=RELOC-NO-PIE %s +# RUN: llvm-bolt %t-no-pie-no-init.exe -o %t-no-pie-no-init --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-EP %s +# RUN: llvm-readelf -hds %t-no-pie-no-init | FileCheck --check-prefix=CHECK-NO-PIE-NO-INIT-EP %s + +## With init: dynamic section should contain DT_INIT +# DYN-INIT: (INIT) + +## Without init: dynamic section should only contain DT_INIT_ARRAY +# DYN-NO-INIT-NOT: (INIT) +# DYN-NO-INIT: (INIT_ARRAY) +# DYN-NO-INIT: (INIT_ARRAYSZ) + +## With interp program header (executable) +# PH-INTERP: Program Headers: +# PH-INTERP: INTERP + +## Without interp program header (shared library) +# PH-INTERP-SHARED: Program Headers: +# PH-INTERP-SHARED-NOT: INTERP + +## With PIE: binary should have relative relocations +# RELOC-PIE: R_AARCH64_RELATIVE + +## With PIE: binary should have relative relocations +# RELOC-SHARED-PIE: R_AARCH64_ABS64 + +## Without PIE: binary should not have relative relocations +# RELOC-NO-PIE-NOT: R_AARCH64_RELATIVE + +## Check BOLT output output initialization hook (ELF Header Entry Point) +# CHECK-BOLT-RT-EP: runtime library initialization was hooked via ELF Header Entry Point +# CHECK-BOLT-RT-EP-NOT: runtime library initialization was hooked via DT_INIT +# CHECK-BOLT-RT-EP-NOT: runtime library initialization was hooked via .init_array entry + +## Check BOLT output output initialization hook (DT_INIT) +# CHECK-BOLT-RT-INIT-NOT: runtime library initialization was hooked via ELF Header Entry Point +# CHECK-BOLT-RT-INIT: runtime library initialization was hooked via DT_INIT +# CHECK-BOLT-RT-INIT-NOT: runtime library initialization was hooked via .init_array entry + +## Check BOLT output output initialization hook (.init_array entry) +# CHECK-BOLT-RT-INIT-ARRAY-NOT: runtime library initialization was hooked via ELF Header Entry Point +# CHECK-BOLT-RT-INIT-ARRAY-NOT: runtime library initialization was hooked via DT_INIT +# CHECK-BOLT-RT-INIT-ARRAY: runtime library initialization was hooked via .init_array entry + +## Check that entry point address is set to __bolt_runtime_start for PIE executable with DT_INIT +# CHECK-INIT-EP: ELF Header: +# CHECK-INIT-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Check that the dynamic relocation at .init and .init_array were not patched +# CHECK-INIT-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-INIT-EP-NOT: (INIT) 0x[[#%x, EP_ADDR]] +# CHECK-INIT-EP-NOT: (INIT_ARRAY) 0x[[#%x, EP_ADDR]] +## Check that the new entry point address points to __bolt_runtime_start +# CHECK-INIT-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-INIT-EP: {{0+}}[[#%x, EP_ADDR]] {{.*}} __bolt_runtime_start + +## Check that DT_INIT address is set to __bolt_runtime_start for PIE executable with DT_INIT +# CHECK-INIT-NO-EP: ELF Header: +# CHECK-INIT-NO-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Read Dynamic section DT_INIT and DT_INIT_ARRAY entries +# CHECK-INIT-NO-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-INIT-NO-EP-DAG: (INIT) 0x[[#%x,INIT:]] +# CHECK-INIT-NO-EP-DAG: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Check if ELF entry point address points to _start symbol and new DT_INIT entry points to __bolt_runtime_start +# CHECK-INIT-NO-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-INIT-NO-EP-DAG: {{0+}}[[#%x, EP_ADDR]] {{.*}} _start +# CHECK-INIT-NO-EP-DAG: {{0+}}[[#%x, INIT]] {{.*}} __bolt_runtime_start + +## Check that 1st entry of DT_INIT_ARRAY is set to __bolt_runtime_start and DT_INIT was not changed +# CHECK-INIT-ARRAY-NO-EP: ELF Header: +# CHECK-INIT-ARRAY-NO-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Read Dynamic section DT_INIT and DT_INIT_ARRAY entries +# CHECK-INIT-ARRAY-NO-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-INIT-ARRAY-NO-EP-DAG: (INIT) 0x[[#%x,INIT:]] +# CHECK-INIT-ARRAY-NO-EP-DAG: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Read the dynamic relocation from 1st entry of .init_array +# CHECK-INIT-ARRAY-NO-EP: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-INIT-ARRAY-NO-EP: {{0+}}[[#%x,INIT_ARRAY]] {{.*}} R_AARCH64_RELATIVE [[#%x,INIT_ADDR:]] +# CHECK-INIT-ARRAY-NO-EP-NOT: {{0+}}[[#%x,INIT_ARRAY]] {{.*}} R_AARCH64_RELATIVE [[#%x,INIT]] +## Check that 1st entry of .init_array points to __bolt_runtime_start +# CHECK-INIT-ARRAY-NO-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-INIT-ARRAY-NO-EP-DAG: {{0+}}[[#%x, EP_ADDR]] {{.*}} _start +# CHECK-INIT-ARRAY-NO-EP-DAG: {{[0-9]]*}}: {{0+}}[[#%x, INIT_ADDR]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for PIE executable without DT_INIT +# CHECK-NO-INIT-EP: ELF Header: +# CHECK-NO-INIT-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Check that the dynamic relocation at .init and .init_array were not patched +# CHECK-NO-INIT-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-NO-INIT-EP-NOT: (INIT) 0x[[#%x, EP_ADDR]] +# CHECK-NO-INIT-EP-NOT: (INIT_ARRAY) 0x[[#%x, EP_ADDR]] +## Check that the new entry point address points to __bolt_runtime_start +# CHECK-NO-INIT-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-NO-INIT-EP: {{0+}}[[#%x, EP_ADDR]] {{.*}} __bolt_runtime_start + +## Check that DT_INIT is set to __bolt_runtime_start for shared library with DT_INIT +# CHECK-SHARED-INIT: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-SHARED-INIT-DAG: (INIT) 0x[[#%x, INIT:]] +# CHECK-SHARED-INIT-DAG: (INIT_ARRAY) 0x[[#%x, INIT_ARRAY:]] +## Check that the dynamic relocation at .init_array was not patched +# CHECK-SHARED-INIT: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-SHARED-INIT-NOT: {{0+}}[[#%x, INIT_ARRAY]] {{.*}} R_AARCH64_ABS64 {{0+}}[[#%x, INIT]] +## Check that dynamic section DT_INIT points to __bolt_runtime_start +# CHECK-SHARED-INIT: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-SHARED-INIT: {{0+}}[[#%x, INIT]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for PIE executable without DT_INIT +# CHECK-NO-INIT-NO-EP: ELF Header: +# CHECK-NO-INIT-NO-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +# CHECK-NO-INIT-NO-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-NO-INIT-NO-EP-NOT: (INIT) +# CHECK-NO-INIT-NO-EP: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Read the dynamic relocation from 1st entry of .init_array +# CHECK-NO-INIT-NO-EP: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-NO-INIT-NO-EP: {{0+}}[[#%x,INIT_ARRAY]] {{.*}} R_AARCH64_RELATIVE [[#%x,INIT_ADDR:]] +## Check that 1st entry of .init_array points to __bolt_runtime_start +# CHECK-NO-INIT-NO-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-NO-INIT-NO-EP-DAG: {{0+}}[[#%x, EP_ADDR]] {{.*}} _start +# CHECK-NO-INIT-NO-EP-DAG: {{[0-9]]*}}: {{0+}}[[#%x, INIT_ADDR]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for shared library without DT_INIT +# CHECK-SHARED-NO-INIT: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-SHARED-NO-INIT-NOT: (INIT) +# CHECK-SHARED-NO-INIT: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Read the dynamic relocation from 1st entry of .init_array +# CHECK-SHARED-NO-INIT: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-SHARED-NO-INIT: {{0+}}[[#%x, INIT_ARRAY]] {{.*}} R_AARCH64_ABS64 [[#%x,INIT_ADDR:]] +## Check that 1st entry of .init_array points to __bolt_runtime_start +# CHECK-SHARED-NO-INIT: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-SHARED-NO-INIT: {{[0-9]]*}}: {{0+}}[[#%x, INIT_ADDR]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for non-PIE executable with DT_INIT +# CHECK-NO-PIE-NO-INIT-EP: ELF Header: +# CHECK-NO-PIE-NO-INIT-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Check that the dynamic relocation at .init and .init_array were not patched +# CHECK-NO-PIE-NO-INIT-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-NO-PIE-NO-INIT-EP-NOT: (INIT) 0x[[#%x, EP_ADDR]] +# CHECK-NO-PIE-NO-INIT-EP-NOT: (INIT_ARRAY) 0x[[#%x, EP_ADDR]] +## Check that the new entry point address points to __bolt_runtime_start +# CHECK-NO-PIE-NO-INIT-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-NO-PIE-NO-INIT-EP: {{0+}}[[#%x, EP_ADDR]] {{.*}} __bolt_runtime_start + + .globl _start + .type _start, %function +_start: + # Dummy relocation to force relocation mode. + .reloc 0, R_AARCH64_NONE + ret +.size _start, .-_start + + .globl _init + .type _init, %function +_init: + ret + .size _init, .-_init + + .globl _fini + .type _fini, %function +_fini: + ret + .size _fini, .-_fini + + .section .init_array,"aw" + .align 3 + .dword _init + + .section .fini_array,"aw" + .align 3 + .dword _fini diff --git a/bolt/test/AArch64/inline-bti-dbg.s b/bolt/test/AArch64/inline-bti-dbg.s new file mode 100644 index 000000000000..a0db4589d39a --- /dev/null +++ b/bolt/test/AArch64/inline-bti-dbg.s @@ -0,0 +1,40 @@ +# This test checks that for AArch64 binaries with BTI, we do not inline blocks with indirect tailcalls. +# Same as inline-bti.s, but checks the debug output, and therefore requires assertions. + +# REQUIRES: system-linux, assertions + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o +# RUN: %clang %cflags -O0 %t.o -o %t.exe -Wl,-q -Wl,-z,force-bti +# RUN: llvm-bolt --inline-all %t.exe -o %t.bolt --debug 2>&1 | FileCheck %s + +# For BTI, we should not inline foo. +# CHECK: BOLT-DEBUG: Skipping inlining block with tailcall in _Z3barP1A : .LBB01 to keep BTIs consistent. +# CHECK-NOT: BOLT-INFO: inlined {{[0-9]+}} calls at {{[0-9]+}} call sites in {{[0-9]+}} iteration(s). Change in binary size: {{[0-9]+}} bytes. + + .text + .globl _Z3fooP1A + .type _Z3fooP1A,@function +_Z3fooP1A: + ldr x8, [x0] + ldr w0, [x8] + br x30 + .size _Z3fooP1A, .-_Z3fooP1A + + .globl _Z3barP1A + .type _Z3barP1A,@function +_Z3barP1A: + stp x29, x30, [sp, #-16]! + mov x29, sp + bl _Z3fooP1A + mul w0, w0, w0 + ldp x29, x30, [sp], #16 + ret + .size _Z3barP1A, .-_Z3barP1A + + .globl main + .p2align 2 + .type main,@function +main: + mov w0, wzr + ret + .size main, .-main diff --git a/bolt/test/AArch64/inline-bti.s b/bolt/test/AArch64/inline-bti.s new file mode 100644 index 000000000000..62f6ea6f4b63 --- /dev/null +++ b/bolt/test/AArch64/inline-bti.s @@ -0,0 +1,38 @@ +## This test checks that for AArch64 binaries with BTI, we do not inline blocks with indirect tailcalls. + +# REQUIRES: system-linux + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o +# RUN: %clang %cflags -O0 %t.o -o %t.exe -Wl,-q -Wl,-z,force-bti +# RUN: llvm-bolt --inline-all %t.exe -o %t.bolt | FileCheck %s + +# For BTI, we should not inline foo. +# CHECK-NOT: BOLT-INFO: inlined {{[0-9]+}} calls at {{[0-9]+}} call sites in {{[0-9]+}} iteration(s). Change in binary size: {{[0-9]+}} bytes. + + .text + .globl _Z3fooP1A + .type _Z3fooP1A,@function +_Z3fooP1A: + ldr x8, [x0] + ldr w0, [x8] + br x30 + .size _Z3fooP1A, .-_Z3fooP1A + + .globl _Z3barP1A + .type _Z3barP1A,@function +_Z3barP1A: + stp x29, x30, [sp, #-16]! + mov x29, sp + bl _Z3fooP1A + mul w0, w0, w0 + ldp x29, x30, [sp], #16 + ret + .size _Z3barP1A, .-_Z3barP1A + + .globl main + .p2align 2 + .type main,@function +main: + mov w0, wzr + ret + .size main, .-main diff --git a/bolt/test/AArch64/instrument-no-fini.s b/bolt/test/AArch64/instrument-no-fini.s new file mode 100644 index 000000000000..526ce11080f4 --- /dev/null +++ b/bolt/test/AArch64/instrument-no-fini.s @@ -0,0 +1,34 @@ +# Test that BOLT will produce error by default and pass with instrumentation-sleep-time option + +# REQUIRES: system-linux,bolt-runtime,target=aarch64{{.*}} + +# RUN: llvm-mc -triple aarch64 -filetype=obj %s -o %t.o +# RUN: ld.lld -q -pie -o %t.exe %t.o +# RUN: llvm-readelf -d %t.exe | FileCheck --check-prefix=CHECK-NO-FINI %s +# RUN: not llvm-bolt --instrument -o %t.out %t.exe 2>&1 | FileCheck %s --check-prefix=CHECK-BOLT-FAIL +# RUN: llvm-bolt --instrument --instrumentation-sleep-time=1 -o %t.out %t.exe 2>&1 | FileCheck %s --check-prefix=CHECK-BOLT-PASS + +# CHECK-NO-FINI: INIT +# CHECK-NO-FINI-NOT: FINI +# CHECK-NO-FINI-NOT: FINI_ARRAY + +# CHECK-BOLT-FAIL: Instrumentation needs either DT_FINI or DT_FINI_ARRAY + +# CHECK-BOLT-PASS-NOT: Instrumentation needs either DT_FINI or DT_FINI_ARRAY +# CHECK-BOLT-PASS: runtime library initialization was hooked via DT_INIT + + .text + .globl _start + .type _start, %function +_start: + # BOLT errs when instrumenting without relocations; create a dummy one. + .reloc 0, R_AARCH64_NONE + ret + .size _start, .-_start + + .globl _init + .type _init, %function + # Force DT_INIT to be created (needed for instrumentation). +_init: + ret + .size _init, .-_init diff --git a/bolt/test/AArch64/negate-ra-state-incorrect.s b/bolt/test/AArch64/negate-ra-state-incorrect.s deleted file mode 100644 index 14d2c384a877..000000000000 --- a/bolt/test/AArch64/negate-ra-state-incorrect.s +++ /dev/null @@ -1,78 +0,0 @@ -# This test checks that MarkRAStates pass ignores functions with -# malformed .cfi_negate_ra_state sequences in the input binary. - -# The cases checked are: -# - extra .cfi_negate_ra_state in Signed state: checked in foo, -# - extra .cfi_negate_ra_state in Unsigned state: checked in bar, -# - missing .cfi_negate_ra_state from PSign or PAuth instructions: checked in baz. - -# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o -# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -# RUN: llvm-bolt %t.exe -o %t.exe.bolt --no-threads | FileCheck %s --check-prefix=CHECK-BOLT - -# CHECK-BOLT: BOLT-INFO: inconsistent RAStates in function foo: ptr authenticating inst encountered in Unsigned RA state -# CHECK-BOLT: BOLT-INFO: inconsistent RAStates in function bar: ptr signing inst encountered in Signed RA state -# CHECK-BOLT: BOLT-INFO: inconsistent RAStates in function baz: ptr sign/auth inst without .cfi_negate_ra_state - -# Check that the incorrect functions got ignored, so they are not in the new .text section -# RUN: llvm-objdump %t.exe.bolt -d -j .text | FileCheck %s --check-prefix=CHECK-OBJDUMP -# CHECK-OBJDUMP-NOT: : -# CHECK-OBJDUMP-NOT: : -# CHECK-OBJDUMP-NOT: : - - - .text - .globl foo - .p2align 2 - .type foo,@function -foo: - .cfi_startproc - hint #25 - .cfi_negate_ra_state - mov x1, #0 - .cfi_negate_ra_state // Incorrect CFI in signed state - hint #29 - .cfi_negate_ra_state - ret - .cfi_endproc - .size foo, .-foo - - .text - .globl bar - .p2align 2 - .type bar,@function -bar: - .cfi_startproc - mov x1, #0 - .cfi_negate_ra_state // Incorrect CFI in unsigned state - hint #25 - .cfi_negate_ra_state - mov x1, #0 - hint #29 - .cfi_negate_ra_state - ret - .cfi_endproc - .size bar, .-bar - - .text - .globl baz - .p2align 2 - .type baz,@function -baz: - .cfi_startproc - mov x1, #0 - hint #25 - .cfi_negate_ra_state - mov x1, #0 - hint #29 - // Missing .cfi_negate_ra_state - ret - .cfi_endproc - .size baz, .-baz - - .global _start - .type _start, %function -_start: - b foo - b bar - b baz diff --git a/bolt/test/AArch64/negate-ra-state.s b/bolt/test/AArch64/negate-ra-state.s deleted file mode 100644 index 30786d4ef9f7..000000000000 --- a/bolt/test/AArch64/negate-ra-state.s +++ /dev/null @@ -1,76 +0,0 @@ -# Checking that .cfi-negate_ra_state directives are emitted in the same location as in the input in the case of no optimizations. - -# The foo and bar functions are a pair, with the first signing the return address, -# and the second authenticating it. We have a tailcall between the two. -# This is testing that BOLT can handle functions starting in signed RA state. - -# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o -# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -# RUN: llvm-bolt %t.exe -o %t.exe.bolt --no-threads --print-all | FileCheck %s --check-prefix=CHECK-BOLT - -# Check that the negate-ra-state at the start of bar is not discarded. -# If it was discarded, MarkRAState would report bar as having inconsistent RAStates. -# This is testing the handling of initialRAState on the BinaryFunction. -# CHECK-BOLT-NOT: BOLT-INFO: inconsistent RAStates in function foo -# CHECK-BOLT-NOT: BOLT-INFO: inconsistent RAStates in function bar - -# Check that OpNegateRAState CFIs are generated correctly. -# CHECK-BOLT: Binary Function "foo" after insert-negate-ra-state-pass { -# CHECK-BOLT: paciasp -# CHECK-BOLT-NEXT: OpNegateRAState - -# CHECK-BOLT: DWARF CFI Instructions: -# CHECK-BOLT-NEXT: 0: OpNegateRAState -# CHECK-BOLT-NEXT: End of Function "foo" - -# CHECK-BOLT: Binary Function "bar" after insert-negate-ra-state-pass { -# CHECK-BOLT: OpNegateRAState -# CHECK-BOLT-NEXT: mov x1, #0x0 -# CHECK-BOLT-NEXT: mov x1, #0x1 -# CHECK-BOLT-NEXT: autiasp -# CHECK-BOLT-NEXT: OpNegateRAState -# CHECK-BOLT-NEXT: ret - -# CHECK-BOLT: DWARF CFI Instructions: -# CHECK-BOLT-NEXT: 0: OpNegateRAState -# CHECK-BOLT-NEXT: 1: OpNegateRAState -# CHECK-BOLT-NEXT: End of Function "bar" - -# End of negate-ra-state insertion logs for foo and bar. -# CHECK: Binary Function "_start" after insert-negate-ra-state-pass { - -# Check that the functions are in the new .text section -# RUN: llvm-objdump %t.exe.bolt -d -j .text | FileCheck %s --check-prefix=CHECK-OBJDUMP -# CHECK-OBJDUMP: : -# CHECK-OBJDUMP: : - - - .text - .globl foo - .p2align 2 - .type foo,@function -foo: - .cfi_startproc - paciasp - .cfi_negate_ra_state - mov x1, #0 - b bar - .cfi_endproc - .size foo, .-foo - - - - .text - .globl bar - .p2align 2 - .type bar,@function -bar: - .cfi_startproc - .cfi_negate_ra_state // Indicating that RA is signed from the start of bar. - mov x1, #0 - mov x1, #1 - autiasp - .cfi_negate_ra_state - ret - .cfi_endproc - .size bar, .-bar diff --git a/bolt/test/AArch64/negate-ra-state-disallow.s b/bolt/test/AArch64/pacret-cfi-disallow.s similarity index 100% rename from bolt/test/AArch64/negate-ra-state-disallow.s rename to bolt/test/AArch64/pacret-cfi-disallow.s diff --git a/bolt/test/AArch64/pacret-cfi-incorrect.s b/bolt/test/AArch64/pacret-cfi-incorrect.s new file mode 100644 index 000000000000..68a6fc008ab0 --- /dev/null +++ b/bolt/test/AArch64/pacret-cfi-incorrect.s @@ -0,0 +1,78 @@ +# This test checks that PointerAuthCFIAnalyzer pass ignores functions with +# malformed .cfi_negate_ra_state sequences in the input binary. + +# The cases checked are: +# - extra .cfi_negate_ra_state in Signed state: checked in foo, +# - extra .cfi_negate_ra_state in Unsigned state: checked in bar, +# - missing .cfi_negate_ra_state from PSign or PAuth instructions: checked in baz. + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o +# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q +# RUN: llvm-bolt %t.exe -o %t.exe.bolt -v=1 --no-threads | FileCheck %s --check-prefix=CHECK-BOLT + +# CHECK-BOLT: BOLT-INFO: inconsistent RAStates in function foo: ptr authenticating inst encountered in Unsigned RA state +# CHECK-BOLT: BOLT-INFO: inconsistent RAStates in function bar: ptr signing inst encountered in Signed RA state +# CHECK-BOLT: BOLT-INFO: inconsistent RAStates in function baz: ptr sign/auth inst without .cfi_negate_ra_state + +# Check that the incorrect functions got ignored, so they are not in the new .text section +# RUN: llvm-objdump %t.exe.bolt -d -j .text | FileCheck %s --check-prefix=CHECK-OBJDUMP +# CHECK-OBJDUMP-NOT: : +# CHECK-OBJDUMP-NOT: : +# CHECK-OBJDUMP-NOT: : + + + .text + .globl foo + .p2align 2 + .type foo,@function +foo: + .cfi_startproc + hint #25 + .cfi_negate_ra_state + mov x1, #0 + .cfi_negate_ra_state // Incorrect CFI in signed state + hint #29 + .cfi_negate_ra_state + ret + .cfi_endproc + .size foo, .-foo + + .text + .globl bar + .p2align 2 + .type bar,@function +bar: + .cfi_startproc + mov x1, #0 + .cfi_negate_ra_state // Incorrect CFI in unsigned state + hint #25 + .cfi_negate_ra_state + mov x1, #0 + hint #29 + .cfi_negate_ra_state + ret + .cfi_endproc + .size bar, .-bar + + .text + .globl baz + .p2align 2 + .type baz,@function +baz: + .cfi_startproc + mov x1, #0 + hint #25 + .cfi_negate_ra_state + mov x1, #0 + hint #29 + // Missing .cfi_negate_ra_state + ret + .cfi_endproc + .size baz, .-baz + + .global _start + .type _start, %function +_start: + b foo + b bar + b baz diff --git a/bolt/test/AArch64/negate-ra-state-reorder.s b/bolt/test/AArch64/pacret-cfi-reorder.s similarity index 100% rename from bolt/test/AArch64/negate-ra-state-reorder.s rename to bolt/test/AArch64/pacret-cfi-reorder.s diff --git a/bolt/test/AArch64/pacret-cfi.s b/bolt/test/AArch64/pacret-cfi.s new file mode 100644 index 000000000000..d3206294a4f7 --- /dev/null +++ b/bolt/test/AArch64/pacret-cfi.s @@ -0,0 +1,76 @@ +# Checking that .cfi-negate_ra_state directives are emitted in the same location as in the input in the case of no optimizations. + +# The foo and bar functions are a pair, with the first signing the return address, +# and the second authenticating it. We have a tailcall between the two. +# This is testing that BOLT can handle functions starting in signed RA state. + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o +# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q +# RUN: llvm-bolt %t.exe -o %t.exe.bolt --no-threads --print-all | FileCheck %s --check-prefix=CHECK-BOLT + +# Check that the negate-ra-state at the start of bar is not discarded. +# If it was discarded, PointerAuthCFIAnalyzer would report bar as having inconsistent RAStates. +# This is testing the handling of initialRAState on the BinaryFunction. +# CHECK-BOLT-NOT: BOLT-INFO: inconsistent RAStates in function foo +# CHECK-BOLT-NOT: BOLT-INFO: inconsistent RAStates in function bar + +# Check that OpNegateRAState CFIs are generated correctly. +# CHECK-BOLT: Binary Function "foo" after pointer-auth-cfi-fixup { +# CHECK-BOLT: paciasp +# CHECK-BOLT-NEXT: OpNegateRAState + +# CHECK-BOLT: DWARF CFI Instructions: +# CHECK-BOLT-NEXT: 0: OpNegateRAState +# CHECK-BOLT-NEXT: End of Function "foo" + +# CHECK-BOLT: Binary Function "bar" after pointer-auth-cfi-fixup { +# CHECK-BOLT: OpNegateRAState +# CHECK-BOLT-NEXT: mov x1, #0x0 +# CHECK-BOLT-NEXT: mov x1, #0x1 +# CHECK-BOLT-NEXT: autiasp +# CHECK-BOLT-NEXT: OpNegateRAState +# CHECK-BOLT-NEXT: ret + +# CHECK-BOLT: DWARF CFI Instructions: +# CHECK-BOLT-NEXT: 0: OpNegateRAState +# CHECK-BOLT-NEXT: 1: OpNegateRAState +# CHECK-BOLT-NEXT: End of Function "bar" + +# End of negate-ra-state insertion logs for foo and bar. +# CHECK: Binary Function "_start" after pointer-auth-cfi-fixup { + +# Check that the functions are in the new .text section +# RUN: llvm-objdump %t.exe.bolt -d -j .text | FileCheck %s --check-prefix=CHECK-OBJDUMP +# CHECK-OBJDUMP: : +# CHECK-OBJDUMP: : + + + .text + .globl foo + .p2align 2 + .type foo,@function +foo: + .cfi_startproc + paciasp + .cfi_negate_ra_state + mov x1, #0 + b bar + .cfi_endproc + .size foo, .-foo + + + + .text + .globl bar + .p2align 2 + .type bar,@function +bar: + .cfi_startproc + .cfi_negate_ra_state // Indicating that RA is signed from the start of bar. + mov x1, #0 + mov x1, #1 + autiasp + .cfi_negate_ra_state + ret + .cfi_endproc + .size bar, .-bar diff --git a/bolt/test/AArch64/pacret-split-funcs.s b/bolt/test/AArch64/pacret-split-funcs.s index 27b347104552..45eeb9aafc7e 100644 --- a/bolt/test/AArch64/pacret-split-funcs.s +++ b/bolt/test/AArch64/pacret-split-funcs.s @@ -11,7 +11,7 @@ # Checking that we don't see any OpNegateRAState CFIs before the insertion pass. # CHECK-NOT: OpNegateRAState -# CHECK: Binary Function "foo" after insert-negate-ra-state-pass +# CHECK: Binary Function "foo" after pointer-auth-cfi-fixup # CHECK: paciasp # CHECK-NEXT: OpNegateRAState @@ -24,7 +24,7 @@ # CHECK-NEXT: OpNegateRAState # CHECK-NEXT: ret -# End of the insert-negate-ra-state-pass logs +# End of the pointer-auth-cfi-fixup logs # CHECK: Binary Function "foo" after finalize-functions .text diff --git a/bolt/test/AArch64/safe-icf.s b/bolt/test/AArch64/safe-icf.s new file mode 100644 index 000000000000..bca28dfba6fd --- /dev/null +++ b/bolt/test/AArch64/safe-icf.s @@ -0,0 +1,73 @@ +# Test BOLT can do safe ICF for AArch64. + +# REQUIRES: system-linux,asserts + +# RUN: %clang %cflags -x assembler-with-cpp %s -o %t1.so -Wl,-q \ +# RUN: -Wl,-z,notext -DREF_BY_NON_CONTROL_FLOW_INSTR +# RUN: %clang %cflags -x assembler-with-cpp %s -o %t2.so -Wl,-q \ +# RUN: -Wl,-z,notext -DREF_IN_RW_DATA_SEC +# RUN: %clang %cflags -x assembler-with-cpp %s -o %t3.so -Wl,-q \ +# RUN: -Wl,-z,notext -DNO_DUMMY_TEXT_RELOC + +# RUN: llvm-bolt %t1.so -o %t.bolt --no-threads --debug-only=bolt-icf \ +# RUN: --icf=all 2>&1 | FileCheck %s --check-prefix=ICF-ALL +# RUN: llvm-bolt %t2.so -o %t.bolt --no-threads --debug-only=bolt-icf \ +# RUN: --icf=all 2>&1 | FileCheck %s --check-prefix=ICF-ALL +# RUN: llvm-bolt %t3.so -o %t.bolt --no-threads --debug-only=bolt-icf \ +# RUN: --icf=all 2>&1 | FileCheck %s --check-prefix=ICF-ALL + +# RUN: llvm-bolt %t1.so -o %t.bolt --no-threads --debug-only=bolt-icf \ +# RUN: --icf=safe 2>&1 | FileCheck %s --check-prefix=ICF-SAFE +# RUN: llvm-bolt %t2.so -o %t.bolt --no-threads --debug-only=bolt-icf \ +# RUN: --icf=safe 2>&1 | FileCheck %s --check-prefix=ICF-SAFE + +# RUN: not llvm-bolt %t3.so -o %t.bolt --icf=safe 2>&1 | FileCheck %s \ +# RUN: --check-prefix=ERROR + +# ICF-ALL: folding bar into foo +# ICF-SAFE: skipping function with reference taken foo +# ERROR: binary built without relocations. Safe ICF is not supported + + .text + + .global foo + .type foo, %function +foo: + mov x0, #0x10 + ret + + .global bar + .type bar, %function +bar: + mov x0, #0x10 + ret + +#if defined(REF_IN_RW_DATA_SEC) && !defined(NO_DUMMY_TEXT_RELOC) + # Dummy relocation to force relocation mode + .reloc 0, R_AARCH64_NONE +#endif + +#if defined(REF_BY_NON_CONTROL_FLOW_INSTR) + .global random + .type random, %function +random: + adrp x8, foo + add x8, x8, :lo12:foo + br x8 +#endif + +#if defined(REF_IN_RW_DATA_SEC) + .data + .balign 8 + .global funcptr +funcptr: + .xword foo +#endif + + .section .rodata + .global _ZTVxx + .balign 8 +_ZTVxx: + .xword foo + .xword bar + .size _ZTVxx, .-_ZTVxx diff --git a/bolt/test/AArch64/validate-branch-target.s b/bolt/test/AArch64/validate-branch-target.s new file mode 100644 index 000000000000..b187fe762dea --- /dev/null +++ b/bolt/test/AArch64/validate-branch-target.s @@ -0,0 +1,36 @@ +## Test that BOLT errs when detecting the target +## of a direct call/branch is a invalid instruction + +# REQUIRES: system-linux +# RUN: rm -rf %t && mkdir -p %t && cd %t +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-linux %s -o main.o +# RUN: %clang %cflags %t/main.o -o main.exe -Wl,-q +# RUN: llvm-bolt %t/main.exe -o %t/main.exe.bolt -lite=0 2>&1 | FileCheck %s --check-prefix=CHECK-TARGETS + +# CHECK-TARGETS: BOLT-WARNING: corrupted control flow detected in function external_corrupt: an external branch/call targets an invalid instruction in function external_func at address 0x{{[0-9a-f]+}}; ignoring both functions +# CHECK-TARGETS: BOLT-WARNING: corrupted control flow detected in function internal_corrupt: an internal branch/call targets an invalid instruction at address 0x{{[0-9a-f]+}}; ignoring this function + + +.globl internal_corrupt +.type internal_corrupt,@function +internal_corrupt: + b constant_island_0 // targeting the data in code +constant_island_0: + .word 0xffffffff +.size internal_corrupt,.-internal_corrupt + + +.globl external_corrupt +.type external_corrupt,@function +external_corrupt: + b constant_island_1 // targeting the data in code externally +.size external_corrupt,.-external_corrupt + +.globl external_func +.type external_func,@function +external_func: + add x0, x0, x1 +constant_island_1: + .word 0xffffffff // data in code + ret +.size external_func,.-external_func diff --git a/bolt/test/AArch64/veneer-bolt.s b/bolt/test/AArch64/veneer-bolt.s new file mode 100644 index 000000000000..84d053f863c2 --- /dev/null +++ b/bolt/test/AArch64/veneer-bolt.s @@ -0,0 +1,56 @@ +## Check that llvm-bolt correctly handles veneers in lite mode. + +# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o +# RUN: link_fdata %s %t.o %t.fdata +# RUN: llvm-strip --strip-unneeded %t.o +# RUN: %clang %cflags %t.o -o %t.exe -nostdlib -Wl,-q +# RUN: llvm-bolt %t.exe -o %t.bolt --lite=1 --data %t.fdata \ +# RUN: --print-normalized 2>&1 | FileCheck %s --check-prefix=CHECK-VENEER + +## Constant islands at the end of functions foo(), bar(), and _start() make each +## one of them ~112MB in size. Thus the total code size exceeds 300MB. + + .text + .global foo + .type foo, %function +foo: + bl _start + bl bar + ret + .space 0x7000000 + .size foo, .-foo + + .global bar + .type bar, %function +bar: + bl foo + bl _start + ret + .space 0x7000000 + .size bar, .-bar + + .global hot + .type hot, %function +hot: +# FDATA: 0 [unknown] 0 1 hot 0 0 100 + bl foo + bl bar + bl _start + ret + .size hot, .-hot + +## Check that BOLT sees the call to foo, not to its veneer. +# CHECK-VENEER-LABEL: Binary Function "hot" +# CHECK-VENEER: bl +# CHECK-VENEER-SAME: {{[[:space:]]foo[[:space:]]}} + + .global _start + .type _start, %function +_start: + bl foo + bl bar + bl hot + ret + .space 0x7000000 + .size _start, .-_start + diff --git a/bolt/test/X86/hook-init.s b/bolt/test/X86/hook-init.s new file mode 100644 index 000000000000..3184541f040b --- /dev/null +++ b/bolt/test/X86/hook-init.s @@ -0,0 +1,221 @@ +## Test the different ways of hooking the init function for instrumentation (via +## entry point, DT_INIT and via DT_INIT_ARRAY). We test the latter for both PIE +## and non-PIE binaries because of the different ways of handling relocations +## (static or dynamic), executable and shared library. +## All tests perform the following steps: +## - Compile and link for the case to be tested +## - Some sanity-checks on the dynamic section and relocations in the binary to +## verify it has the shape we want for testing: +## - INTERP in Program Headers +## - DT_INIT or DT_INIT_ARRAY in dynamic section +## - No relative relocations for non-PIE +## - Instrument (with extra --runtime-lib-init-hook=init/init_array options +## in some cases) +## - Verify generated binary +# REQUIRES: system-linux,bolt-runtime,target=x86_64-{{.*}} + +# RUN: %clang %cflags -pie %s -Wl,-q -o %t.exe +# RUN: llvm-readelf -d %t.exe | FileCheck --check-prefix=DYN-INIT %s +# RUN: llvm-readelf -l %t.exe | FileCheck --check-prefix=PH-INTERP %s +# RUN: llvm-readelf -r %t.exe | FileCheck --check-prefix=RELOC-PIE %s +# RUN: llvm-bolt %t.exe -o %t --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-EP %s +# RUN: llvm-readelf -hdrs %t | FileCheck --check-prefix=CHECK-INIT-EP %s +# RUN: llvm-bolt %t.exe -o %t-no-ep --instrument --runtime-lib-init-hook=init | FileCheck --check-prefix=CHECK-BOLT-RT-INIT %s +# RUN: llvm-readelf -hdrs %t-no-ep | FileCheck --check-prefix=CHECK-INIT-NO-EP %s +# RUN: llvm-bolt %t.exe -o %t-no-ep --instrument --runtime-lib-init-hook=init_array | FileCheck --check-prefix=CHECK-BOLT-RT-INIT-ARRAY %s +# RUN: llvm-readelf -hdrs %t-no-ep | FileCheck --check-prefix=CHECK-INIT-ARRAY-NO-EP %s + +# RUN: %clang -shared %cflags -pie %s -Wl,-q -o %t-shared.exe +# RUN: llvm-readelf -d %t-shared.exe | FileCheck --check-prefix=DYN-INIT %s +# RUN: llvm-readelf -l %t-shared.exe | FileCheck --check-prefix=PH-INTERP-SHARED %s +# RUN: llvm-readelf -r %t-shared.exe | FileCheck --check-prefix=RELOC-SHARED-PIE %s +# RUN: llvm-bolt %t-shared.exe -o %t-shared --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-INIT %s +# RUN: llvm-readelf -hdrs %t-shared | FileCheck --check-prefix=CHECK-SHARED-INIT %s + +# RUN: %clang %cflags -pie %s -Wl,-q,-init=0 -o %t-no-init.exe +# RUN: llvm-readelf -d %t-no-init.exe | FileCheck --check-prefix=DYN-NO-INIT %s +# RUN: llvm-readelf -l %t-no-init.exe | FileCheck --check-prefix=PH-INTERP %s +# RUN: llvm-readelf -r %t-no-init.exe | FileCheck --check-prefix=RELOC-PIE %s +# RUN: llvm-bolt %t-no-init.exe -o %t-no-init --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-EP %s +# RUN: llvm-readelf -hdrs %t-no-init | FileCheck --check-prefix=CHECK-NO-INIT-EP %s +# RUN: llvm-bolt %t-no-init.exe -o %t-no-init-no-ep --instrument --runtime-lib-init-hook=init | FileCheck --check-prefix=CHECK-BOLT-RT-INIT-ARRAY %s +# RUN: llvm-readelf -hdrs %t-no-init-no-ep | FileCheck --check-prefix=CHECK-NO-INIT-NO-EP %s + +# RUN: %clang -shared %cflags -pie %s -Wl,-q,-init=0 -o %t-shared-no-init.exe +# RUN: llvm-readelf -d %t-shared-no-init.exe | FileCheck --check-prefix=DYN-NO-INIT %s +# RUN: llvm-readelf -l %t-shared-no-init.exe | FileCheck --check-prefix=PH-INTERP-SHARED %s +# RUN: llvm-readelf -r %t-shared-no-init.exe | FileCheck --check-prefix=RELOC-SHARED-PIE %s +# RUN: llvm-bolt %t-shared-no-init.exe -o %t-shared-no-init --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-INIT-ARRAY %s +# RUN: llvm-readelf -drs %t-shared-no-init | FileCheck --check-prefix=CHECK-SHARED-NO-INIT %s + +## Create a dummy shared library to link against to force creation of the dynamic section. +# RUN: %clang %cflags %p/../Inputs/stub.c -fPIC -shared -o %t-stub.so +# RUN: %clang %cflags %s -no-pie -Wl,-q,-init=0 %t-stub.so -o %t-no-pie-no-init.exe +# RUN: llvm-readelf -r %t-no-pie-no-init.exe | FileCheck --check-prefix=RELOC-NO-PIE %s +# RUN: llvm-bolt %t-no-pie-no-init.exe -o %t-no-pie-no-init --instrument | FileCheck --check-prefix=CHECK-BOLT-RT-EP %s +# RUN: llvm-readelf -hds %t-no-pie-no-init | FileCheck --check-prefix=CHECK-NO-PIE-NO-INIT-EP %s + +## With init: dynamic section should contain DT_INIT +# DYN-INIT: (INIT) + +## Without init: dynamic section should only contain DT_INIT_ARRAY +# DYN-NO-INIT-NOT: (INIT) +# DYN-NO-INIT: (INIT_ARRAY) +# DYN-NO-INIT: (INIT_ARRAYSZ) + +## With interp program header (executable) +# PH-INTERP: Program Headers: +# PH-INTERP: INTERP + +## Without interp program header (shared library) +# PH-INTERP-SHARED: Program Headers: +# PH-INTERP-SHARED-NOT: INTERP + +## With PIE: binary should have relative relocations +# RELOC-PIE: R_X86_64_RELATIVE + +## With PIE: binary should have relative relocations +# RELOC-SHARED-PIE: R_X86_64_64 + +## Without PIE: binary should not have relative relocations +# RELOC-NO-PIE-NOT: R_X86_64_RELATIVE + +## Check BOLT output output initialization hook (ELF Header Entry Point) +# CHECK-BOLT-RT-EP: runtime library initialization was hooked via ELF Header Entry Point +# CHECK-BOLT-RT-EP-NOT: runtime library initialization was hooked via DT_INIT +# CHECK-BOLT-RT-EP-NOT: runtime library initialization was hooked via .init_array entry + +## Check BOLT output output initialization hook (DT_INIT) +# CHECK-BOLT-RT-INIT-NOT: runtime library initialization was hooked via ELF Header Entry Point +# CHECK-BOLT-RT-INIT: runtime library initialization was hooked via DT_INIT +# CHECK-BOLT-RT-INIT-NOT: runtime library initialization was hooked via .init_array entry + +## Check BOLT output output initialization hook (1st entry of .init_array) +# CHECK-BOLT-RT-INIT-ARRAY-NOT: runtime library initialization was hooked via ELF Header Entry Point +# CHECK-BOLT-RT-INIT-ARRAY-NOT: runtime library initialization was hooked via DT_INIT +# CHECK-BOLT-RT-INIT-ARRAY: runtime library initialization was hooked via .init_array entry + +## Check that entry point address is set to __bolt_runtime_start for PIE executable with DT_INIT +# CHECK-INIT-EP: ELF Header: +# CHECK-INIT-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Check that the dynamic relocation at .init and .init_array were not patched +# CHECK-INIT-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-INIT-EP-NOT: (INIT) 0x[[#%x, EP_ADDR]] +# CHECK-INIT-EP-NOT: (INIT_ARRAY) 0x[[#%x, EP_ADDR]] +## Check that the new entry point address points to __bolt_runtime_start +# CHECK-INIT-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-INIT-EP: {{0+}}[[#%x, EP_ADDR]] {{.*}} __bolt_runtime_start + +## Check that DT_INIT address is set to __bolt_runtime_start for PIE executable with DT_INIT +# CHECK-INIT-NO-EP: ELF Header: +# CHECK-INIT-NO-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Read Dynamic section DT_INIT and DT_INIT_ARRAY entries +# CHECK-INIT-NO-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-INIT-NO-EP-DAG: (INIT) 0x[[#%x,INIT:]] +# CHECK-INIT-NO-EP-DAG: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Check if ELF entry point address points to _start symbol and new DT_INIT entry points to __bolt_runtime_start +# CHECK-INIT-NO-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-INIT-NO-EP-DAG: {{0+}}[[#%x, EP_ADDR]] {{.*}} _start +# CHECK-INIT-NO-EP-DAG: {{0+}}[[#%x, INIT]] {{.*}} __bolt_runtime_start + +## Check that 1st entry of DT_INIT_ARRAY is set to __bolt_runtime_start and DT_INIT was not changed +# CHECK-INIT-ARRAY-NO-EP: ELF Header: +# CHECK-INIT-ARRAY-NO-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Read Dynamic section DT_INIT and DT_INIT_ARRAY entries +# CHECK-INIT-ARRAY-NO-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-INIT-ARRAY-NO-EP-DAG: (INIT) 0x[[#%x,INIT:]] +# CHECK-INIT-ARRAY-NO-EP-DAG: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Read the dynamic relocation from 1st entry of .init_array +# CHECK-INIT-ARRAY-NO-EP: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-INIT-ARRAY-NO-EP: {{0+}}[[#%x,INIT_ARRAY]] {{.*}} R_X86_64_RELATIVE [[#%x,INIT_ADDR:]] +# CHECK-INIT-ARRAY-NO-EP-NOT: {{0+}}[[#%x,INIT_ARRAY]] {{.*}} R_X86_64_RELATIVE [[#%x,INIT]] +## Check that 1st entry of .init_array points to __bolt_runtime_start +# CHECK-INIT-ARRAY-NO-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-INIT-ARRAY-NO-EP-DAG: {{0+}}[[#%x, EP_ADDR]] {{.*}} _start +# CHECK-INIT-ARRAY-NO-EP-DAG: {{[0-9]]*}}: {{0+}}[[#%x, INIT_ADDR]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for PIE executable without DT_INIT +# CHECK-NO-INIT-EP: ELF Header: +# CHECK-NO-INIT-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Check that the dynamic relocation at .init and .init_array were not patched +# CHECK-NO-INIT-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-NO-INIT-EP-NOT: (INIT) 0x[[#%x, EP_ADDR]] +# CHECK-NO-INIT-EP-NOT: (INIT_ARRAY) 0x[[#%x, EP_ADDR]] +## Check that the new entry point address points to __bolt_runtime_start +# CHECK-NO-INIT-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-NO-INIT-EP: {{0+}}[[#%x, EP_ADDR]] {{.*}} __bolt_runtime_start + +## Check that DT_INIT is set to __bolt_runtime_start for shared library with DT_INIT +# CHECK-SHARED-INIT: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-SHARED-INIT-DAG: (INIT) 0x[[#%x, INIT:]] +# CHECK-SHARED-INIT-DAG: (INIT_ARRAY) 0x[[#%x, INIT_ARRAY:]] +## Check that the dynamic relocation at .init_array was not patched +# CHECK-SHARED-INIT: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-SHARED-INIT-NOT: {{0+}}[[#%x, INIT_ARRAY]] {{.*}} R_X86_64_64 {{0+}}[[#%x, INIT]] +## Check that dynamic section DT_INIT points to __bolt_runtime_start +# CHECK-SHARED-INIT: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-SHARED-INIT: {{0+}}[[#%x, INIT]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for PIE executable without DT_INIT +# CHECK-NO-INIT-NO-EP: ELF Header: +# CHECK-NO-INIT-NO-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +# CHECK-NO-INIT-NO-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-NO-INIT-NO-EP-NOT: (INIT) +# CHECK-NO-INIT-NO-EP: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Read the dynamic relocation from 1st entry of .init_array +# CHECK-NO-INIT-NO-EP: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-NO-INIT-NO-EP: {{0+}}[[#%x,INIT_ARRAY]] {{.*}} R_X86_64_RELATIVE [[#%x,INIT_ADDR:]] +## Check that 1st entry of .init_array points to __bolt_runtime_start +# CHECK-NO-INIT-NO-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-NO-INIT-NO-EP-DAG: {{0+}}[[#%x, EP_ADDR]] {{.*}} _start +# CHECK-NO-INIT-NO-EP-DAG: {{[0-9]]*}}: {{0+}}[[#%x, INIT_ADDR]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for shared library without DT_INIT +# CHECK-SHARED-NO-INIT: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-SHARED-NO-INIT-NOT: (INIT) +# CHECK-SHARED-NO-INIT: (INIT_ARRAY) 0x[[#%x,INIT_ARRAY:]] +## Read the dynamic relocation from 1st entry of .init_array +# CHECK-SHARED-NO-INIT: Relocation section '.rela.dyn' at offset {{.*}} contains {{.*}} entries +# CHECK-SHARED-NO-INIT: {{0+}}[[#%x, INIT_ARRAY]] {{.*}} R_X86_64_64 [[#%x,INIT_ADDR:]] +## Check that 1st entry of .init_array points to __bolt_runtime_start +# CHECK-SHARED-NO-INIT: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-SHARED-NO-INIT: {{[0-9]]*}}: {{0+}}[[#%x, INIT_ADDR]] {{.*}} __bolt_runtime_start + +## Check that entry point address is set to __bolt_runtime_start for non-PIE executable with DT_INIT +# CHECK-NO-PIE-NO-INIT-EP: ELF Header: +# CHECK-NO-PIE-NO-INIT-EP: Entry point address: 0x[[#%x,EP_ADDR:]] +## Check that the dynamic relocation at .init and .init_array were not patched +# CHECK-NO-PIE-NO-INIT-EP: Dynamic section at offset {{.*}} contains {{.*}} entries: +# CHECK-NO-PIE-NO-INIT-EP-NOT: (INIT) 0x[[#%x, EP_ADDR]] +# CHECK-NO-PIE-NO-INIT-EP-NOT: (INIT_ARRAY) 0x[[#%x, EP_ADDR]] +## Check that the new entry point address points to __bolt_runtime_start +# CHECK-NO-PIE-NO-INIT-EP: Symbol table '.symtab' contains {{.*}} entries: +# CHECK-NO-PIE-NO-INIT-EP: {{0+}}[[#%x, EP_ADDR]] {{.*}} __bolt_runtime_start + + .globl _start + .type _start, %function +_start: + # Dummy relocation to force relocation mode. + .reloc 0, R_X86_64_NONE + retq +.size _start, .-_start + + .globl _init + .type _init, %function +_init: + retq + .size _init, .-_init + + .globl _fini + .type _fini, %function +_fini: + retq + .size _fini, .-_fini + + .section .init_array,"aw" + .align 8 + .quad _init + + .section .fini_array,"aw" + .align 8 + .quad _fini diff --git a/bolt/test/X86/instrument-no-fini.s b/bolt/test/X86/instrument-no-fini.s new file mode 100644 index 000000000000..fff23761d149 --- /dev/null +++ b/bolt/test/X86/instrument-no-fini.s @@ -0,0 +1,34 @@ +# Test that BOLT will produce error by default and pass with instrumentation-sleep-time option + +# REQUIRES: system-linux,bolt-runtime,target=x86_64-{{.*}} + +# RUN: llvm-mc -triple x86_64 -filetype=obj %s -o %t.o +# RUN: ld.lld -q -pie -o %t.exe %t.o +# RUN: llvm-readelf -d %t.exe | FileCheck --check-prefix=CHECK-NO-FINI %s +# RUN: not llvm-bolt --instrument -o %t.out %t.exe 2>&1 | FileCheck %s --check-prefix=CHECK-BOLT-FAIL +# RUN: llvm-bolt --instrument --instrumentation-sleep-time=1 -o %t.out %t.exe 2>&1 | FileCheck %s --check-prefix=CHECK-BOLT-PASS + +# CHECK-NO-FINI: INIT +# CHECK-NO-FINI-NOT: FINI +# CHECK-NO-FINI-NOT: FINI_ARRAY + +# CHECK-BOLT-FAIL: Instrumentation needs either DT_FINI or DT_FINI_ARRAY + +# CHECK-BOLT-PASS-NOT: Instrumentation needs either DT_FINI or DT_FINI_ARRAY +# CHECK-BOLT-PASS: runtime library initialization was hooked via DT_INIT + + .text + .globl _start + .type _start, %function +_start: + # BOLT errs when instrumenting without relocations; create a dummy one. + .reloc 0, R_X86_64_NONE + retq + .size _start, .-_start + + .globl _init + .type _init, %function + # Force DT_INIT to be created (needed for instrumentation). +_init: + retq + .size _init, .-_init diff --git a/bolt/test/X86/internal-call-instrument-so.s b/bolt/test/X86/internal-call-instrument-so.s index 99e5b2922140..fe23bc61afa3 100644 --- a/bolt/test/X86/internal-call-instrument-so.s +++ b/bolt/test/X86/internal-call-instrument-so.s @@ -5,7 +5,7 @@ # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o # Delete our BB symbols so BOLT doesn't mark them as entry points # RUN: llvm-strip --strip-unneeded %t.o -# RUN: ld.lld %t.o -o %t.exe -q -shared -fini=_fini +# RUN: ld.lld %t.o -o %t.exe -q -shared -fini=_fini -init=_init # RUN: llvm-bolt --instrument %t.exe --relocs -o %t.out .text @@ -48,6 +48,13 @@ _fini: hlt .size _fini, .-_fini + .globl _init + .type _init, %function + .p2align 4 +_init: + retq + .size _init, .-_init + .data .globl var var: diff --git a/bolt/test/X86/unclaimed-jt-entries.s b/bolt/test/X86/unclaimed-jt-entries.s index 31b72c47125a..6cb0b081e2f7 100644 --- a/bolt/test/X86/unclaimed-jt-entries.s +++ b/bolt/test/X86/unclaimed-jt-entries.s @@ -32,12 +32,7 @@ # RUN: llvm-bolt %t.exe -v=1 -o %t.out 2>&1 | FileCheck %s -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in main -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in main -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in main -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in main -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in main -# CHECK: BOLT-WARNING: failed to post-process indirect branches for main +# CHECK: BOLT-WARNING: 11 unclaimed data relocations remain against function main .text .globl main diff --git a/bolt/test/X86/validate-branch-target.s b/bolt/test/X86/validate-branch-target.s new file mode 100644 index 000000000000..3cc5b9154e12 --- /dev/null +++ b/bolt/test/X86/validate-branch-target.s @@ -0,0 +1,33 @@ +## Test that BOLT errs when detecting the target +## of a direct call/branch is a invalid instruction + +# REQUIRES: system-linux +# RUN: rm -rf %t && mkdir -p %t && cd %t +# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o main.o +# RUN: %clang %cflags -pie -Wl,-q %t/main.o -o main.exe +# RUN: llvm-bolt %t/main.exe -o %t/main.exe.bolt -lite=0 2>&1 | FileCheck %s --check-prefix=CHECK-TARGETS + +# CHECK-TARGETS: BOLT-WARNING: corrupted control flow detected in function external_corrupt: an external branch/call targets an invalid instruction in function external_func at address 0x{{[0-9a-f]+}}; ignoring both functions +# CHECK-TARGETS: BOLT-WARNING: corrupted control flow detected in function internal_corrupt: an internal branch/call targets an invalid instruction at address 0x{{[0-9a-f]+}}; ignoring this function + + +.globl internal_corrupt +.type internal_corrupt,@function +internal_corrupt: + jb data_in_code + 1 # targeting the data in code, and jump into the middle of 'xorb' instruction +data_in_code: + .byte 0x34, 0x01 # data in code, will be disassembled as 'xorb 0x1, %al' +.size internal_corrupt,.-internal_corrupt + + +.globl external_corrupt +.type external_corrupt,@function +external_corrupt: + jb external_func + 1 # targeting the middle of normal instruction externally +.size external_corrupt,.-external_corrupt + +.globl external_func +.type external_func,@function +external_func: + addq $1, %rax # normal instruction +.size external_func,.-external_func diff --git a/bolt/test/dwarf5-missing-dwo.c b/bolt/test/dwarf5-missing-dwo.c new file mode 100644 index 000000000000..25de1a53aa5a --- /dev/null +++ b/bolt/test/dwarf5-missing-dwo.c @@ -0,0 +1,18 @@ +// Check that llvm-bolt correctly reports a missing DWO file while updating +// debug info. +// +// RUN: %clang %cflags -g -dwarf5 -gsplit-dwarf=single -c %s -o %t.o +// RUN: %clang %cflags %t.o -o %t.exe -Wl,-q +// RUN: rm %t.o +// RUN: not llvm-bolt %t.exe -o %t.bolt --update-debug-sections \ +// RUN: 2>&1 | FileCheck %s -DDWO=%t.o +// +// Check that llvm-bolt succeeds on the same binary with disabled debug info +// update. +// +// RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections=0 + +// CHECK: BOLT-ERROR: unable to load [[DWO]] +// CHECK-NEXT: BOLT-ERROR: 1 required DWO file(s) not found + +int main() { return 0; } diff --git a/bolt/test/runtime/AArch64/pacret-function-split.cpp b/bolt/test/runtime/AArch64/pacret-eh-function-split.cpp similarity index 100% rename from bolt/test/runtime/AArch64/pacret-function-split.cpp rename to bolt/test/runtime/AArch64/pacret-eh-function-split.cpp diff --git a/bolt/test/runtime/AArch64/negate-ra-state.cpp b/bolt/test/runtime/AArch64/pacret-eh.cpp similarity index 100% rename from bolt/test/runtime/AArch64/negate-ra-state.cpp rename to bolt/test/runtime/AArch64/pacret-eh.cpp diff --git a/bolt/test/runtime/AArch64/pacret-synchronous-unwind.cpp b/bolt/test/runtime/AArch64/pacret-synchronous-unwind.cpp new file mode 100644 index 000000000000..0f5e9a38da2b --- /dev/null +++ b/bolt/test/runtime/AArch64/pacret-synchronous-unwind.cpp @@ -0,0 +1,36 @@ +// Test to demonstrate that functions compiled with synchronous unwind tables +// are ignored by the PointerAuthCFIAnalyzer. +// Exception handling is needed to have _any_ unwind tables, otherwise the +// PointerAuthCFIAnalyzer does not run on these functions, so it does not ignore +// any function. +// +// REQUIRES: system-linux,bolt-runtime +// +// RUN: %clangxx --target=aarch64-unknown-linux-gnu \ +// RUN: -mbranch-protection=pac-ret \ +// RUN: -fno-asynchronous-unwind-tables \ +// RUN: %s -o %t.exe -Wl,-q +// RUN: llvm-bolt %t.exe -o %t.bolt | FileCheck %s --check-prefix=CHECK + +// Number of functions with .cfi-negate-ra-state in the binary is +// platform-dependent. +// CHECK: BOLT-INFO: PointerAuthCFIAnalyzer ran on {{[0-9]+}} functions. +// CHECK-SAME: Ignored {{[0-9]}} functions ({{[0-9.]+}}%) because of CFI +// CHECK-SAME: inconsistencies +// CHECK-NEXT: BOLT-WARNING: PointerAuthCFIAnalyzer only supports +// CHECK-SAME: asynchronous unwind tables. For C compilers, see +// CHECK-SAME: -fasynchronous-unwind-tables. + +#include +#include + +void foo() { throw std::runtime_error("Exception from foo()."); } + +int main() { + try { + foo(); + } catch (const std::exception &e) { + printf("Exception caught: %s\n", e.what()); + } + return 0; +} diff --git a/bolt/test/runtime/X86/instrument-wrong-target.s b/bolt/test/runtime/X86/instrument-wrong-target.s index 343d93a89ed1..fa40d43f10a0 100644 --- a/bolt/test/runtime/X86/instrument-wrong-target.s +++ b/bolt/test/runtime/X86/instrument-wrong-target.s @@ -19,6 +19,13 @@ _start: ret .size _start, .-_start + .globl _init + .type _init, %function + # Force DT_INIT to be created (needed for instrumentation). +_init: + ret + .size _init, .-_init + .globl _fini .type _fini, %function # Force DT_FINI to be created (needed for instrumentation). diff --git a/bolt/test/runtime/X86/unclaimed-jt-entries.s b/bolt/test/runtime/X86/unclaimed-jt-entries.s index 1725fb808efb..943b7e20a19b 100644 --- a/bolt/test/runtime/X86/unclaimed-jt-entries.s +++ b/bolt/test/runtime/X86/unclaimed-jt-entries.s @@ -18,14 +18,9 @@ # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o # RUN: %clang %cflags %S/Inputs/unclaimed-jt-entries.c -no-pie %t.o -o %t.exe -Wl,-q -# RUN: llvm-bolt %t.exe -v=1 -o %t.out --sequential-disassembly 2>&1 | FileCheck %s +# RUN: llvm-bolt %t.exe -o %t.out 2>&1 | FileCheck %s -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in func -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in func -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in func -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in func -# CHECK: BOLT-WARNING: unclaimed data to code reference (possibly an unrecognized jump table entry) to .Ltmp[[#]] in func -# CHECK: BOLT-WARNING: failed to post-process indirect branches for func +# CHECK: BOLT-WARNING: 11 unclaimed data relocations remain against function func # Run the optimized binary # RUN: %t.out 3 | FileCheck %s --check-prefix=CHECK3 diff --git a/bolt/test/safe-icf-relative-vtable.cpp b/bolt/test/safe-icf-relative-vtable.cpp new file mode 100644 index 000000000000..59ddcc656a5d --- /dev/null +++ b/bolt/test/safe-icf-relative-vtable.cpp @@ -0,0 +1,26 @@ +// Test safe ICF works with binaries that contain relative vtable. + +// REQUIRES: system-linux,asserts + +// RUN: %clang %cxxflags -o %t.so %s -Wl,-q -fno-rtti +// RUN: llvm-bolt %t.so -o %t.bolt --no-threads --icf=safe \ +// RUN: --debug-only=bolt-icf 2>&1 | FileCheck %s + +// RUN: %clang %cxxflags -o %t.so %s -Wl,-q -fno-rtti \ +// RUN: -fexperimental-relative-c++-abi-vtables +// RUN: llvm-bolt %t.so -o %t.bolt --no-threads --icf=safe \ +// RUN: --debug-only=bolt-icf 2>&1 | FileCheck %s + +// CHECK: folding {{.*bar.*}} into {{.*foo.*}} +// CHECK-NOT: skipping function with reference taken {{.*bar.*}} + +class TT { +public: + virtual int foo(int a) { return ++a; } + virtual int bar(int a) { return ++a; } +}; + +int main() { + TT T; + return T.foo(0) + T.bar(1); +} diff --git a/bolt/unittests/CMakeLists.txt b/bolt/unittests/CMakeLists.txt index 64414b83d39f..d47ddc46b738 100644 --- a/bolt/unittests/CMakeLists.txt +++ b/bolt/unittests/CMakeLists.txt @@ -7,3 +7,4 @@ endfunction() add_subdirectory(Core) add_subdirectory(Profile) +add_subdirectory(Passes) diff --git a/bolt/unittests/Core/MCPlusBuilder.cpp b/bolt/unittests/Core/MCPlusBuilder.cpp index 439d72a343ce..e8323e87fe14 100644 --- a/bolt/unittests/Core/MCPlusBuilder.cpp +++ b/bolt/unittests/Core/MCPlusBuilder.cpp @@ -156,6 +156,8 @@ TEST_P(MCPlusBuilderTester, AArch64_BTI) { ASSERT_EQ(II->getOpcode(), AArch64::HINT); ASSERT_EQ(II->getOperand(0).getImm(), 38); ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true)); + BC->MIB->updateBTIVariant(*II, true, false); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); MCInst BTIj; BC->MIB->createBTI(BTIj, false, true); @@ -163,6 +165,8 @@ TEST_P(MCPlusBuilderTester, AArch64_BTI) { ASSERT_EQ(II->getOpcode(), AArch64::HINT); ASSERT_EQ(II->getOperand(0).getImm(), 36); ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true)); + BC->MIB->updateBTIVariant(*II, true, true); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true)); MCInst BTIc; BC->MIB->createBTI(BTIc, true, false); @@ -170,10 +174,14 @@ TEST_P(MCPlusBuilderTester, AArch64_BTI) { ASSERT_EQ(II->getOpcode(), AArch64::HINT); ASSERT_EQ(II->getOperand(0).getImm(), 34); ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); + BC->MIB->updateBTIVariant(*II, false, true); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true)); +#ifndef NDEBUG MCInst BTIinvalid; ASSERT_DEATH(BC->MIB->createBTI(BTIinvalid, false, false), "No target kinds!"); +#endif MCInst Paciasp = MCInstBuilder(AArch64::PACIASP); II = BB->addInstruction(Paciasp); @@ -190,6 +198,122 @@ TEST_P(MCPlusBuilderTester, AArch64_BTI) { ASSERT_TRUE(BC->MIB->isImplicitBTIC(*II)); } +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_empty) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X16); + BC->MIB->insertBTI(*BB, CallInst); + // Check that BTI c is added to the empty block. + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); +} +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_0) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst Inst = MCInstBuilder(AArch64::RET).addReg(AArch64::LR); + BB->addInstruction(Inst); + // BR x16 needs BTI c or BTI j. We prefer adding a BTI c. + MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X16); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); +} + +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_1) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst BTIc; + BC->MIB->createBTI(BTIc, true, false); + BB->addInstruction(BTIc); + // BR x16 needs BTI c or BTI j. We have a BTI c, no change is needed. + MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X16); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); +} + +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_2) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst BTIc; + BC->MIB->createBTI(BTIc, true, false); + BB->addInstruction(BTIc); + // BR x5 needs BTI j + // we have BTI c -> extend it to BTI jc. + MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X5); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true)); +} + +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_3) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst Inst = MCInstBuilder(AArch64::RET).addReg(AArch64::LR); + BB->addInstruction(Inst); + // BR x5 needs BTI j + MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X5); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true)); +} + +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_4) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst Inst = MCInstBuilder(AArch64::RET).addReg(AArch64::LR); + BB->addInstruction(Inst); + // BLR needs BTI c, regardless of the register used. + MCInst CallInst = MCInstBuilder(AArch64::BLR).addReg(AArch64::X5); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); +} + +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_5) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst BTIj; + BC->MIB->createBTI(BTIj, false, true); + BB->addInstruction(BTIj); + // BLR needs BTI c, regardless of the register used. + // We have a BTI j -> extend it to BTI jc. + MCInst CallInst = MCInstBuilder(AArch64::BLR).addReg(AArch64::X5); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true)); +} + +TEST_P(MCPlusBuilderTester, AArch64_insertBTI_6) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true); + std::unique_ptr BB = BF->createBasicBlock(); + MCInst Paciasp = + MCInstBuilder(AArch64::PACIASP).addReg(AArch64::LR).addReg(AArch64::SP); + BB->addInstruction(Paciasp); + // PACI(AB)SP are implicit BTI c, no change needed. + MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X17); + BC->MIB->insertBTI(*BB, CallInst); + auto II = BB->begin(); + ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false)); + ASSERT_TRUE(BC->MIB->isPSignOnLR(*II)); +} + TEST_P(MCPlusBuilderTester, AArch64_CmpJNE) { if (GetParam() != Triple::aarch64) GTEST_SKIP(); diff --git a/bolt/unittests/Passes/CMakeLists.txt b/bolt/unittests/Passes/CMakeLists.txt new file mode 100644 index 000000000000..17ae8802f695 --- /dev/null +++ b/bolt/unittests/Passes/CMakeLists.txt @@ -0,0 +1,30 @@ +set(LLVM_LINK_COMPONENTS + DebugInfoDWARF + Object + MC + ${BOLT_TARGETS_TO_BUILD} + ) + +add_bolt_unittest(PassTests + PointerAuthCFIFixup.cpp + + DISABLE_LLVM_LINK_LLVM_DYLIB + ) + +target_link_libraries(PassTests + PRIVATE + LLVMBOLTCore + LLVMBOLTRewrite + LLVMBOLTPasses + LLVMBOLTProfile + LLVMBOLTUtils + ) + +foreach (tgt ${BOLT_TARGETS_TO_BUILD}) + include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/${tgt} + ${LLVM_BINARY_DIR}/lib/Target/${tgt} + ) + string(TOUPPER "${tgt}" upper) + target_compile_definitions(PassTests PRIVATE "${upper}_AVAILABLE") +endforeach() diff --git a/bolt/unittests/Passes/PointerAuthCFIFixup.cpp b/bolt/unittests/Passes/PointerAuthCFIFixup.cpp new file mode 100644 index 000000000000..0d54b3172427 --- /dev/null +++ b/bolt/unittests/Passes/PointerAuthCFIFixup.cpp @@ -0,0 +1,339 @@ +//===- bolt/unittest/Passes/PointerAuthCFIFixup.cpp ----------------------===// +// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifdef AARCH64_AVAILABLE +#include "AArch64Subtarget.h" +#include "MCTargetDesc/AArch64MCTargetDesc.h" +#endif // AARCH64_AVAILABLE + +#include "bolt/Core/BinaryBasicBlock.h" +#include "bolt/Core/BinaryFunction.h" +#include "bolt/Passes/PointerAuthCFIFixup.h" +#include "bolt/Rewrite/BinaryPassManager.h" +#include "bolt/Rewrite/RewriteInstance.h" +#include "bolt/Utils/CommandLineOpts.h" +#include "llvm/BinaryFormat/ELF.h" +#include "llvm/MC/MCDwarf.h" +#include "llvm/MC/MCInstBuilder.h" +#include "llvm/Support/TargetSelect.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::object; +using namespace llvm::ELF; +using namespace bolt; + +namespace opts { +extern cl::opt PrintPAuthCFIAnalyzer; +} // namespace opts + +namespace { +struct PassTester : public testing::TestWithParam { + void SetUp() override { + initalizeLLVM(); + prepareElf(); + initializeBolt(); + } + +protected: + void initalizeLLVM() { +#define BOLT_TARGET(target) \ + LLVMInitialize##target##TargetInfo(); \ + LLVMInitialize##target##TargetMC(); \ + LLVMInitialize##target##AsmParser(); \ + LLVMInitialize##target##Disassembler(); \ + LLVMInitialize##target##Target(); \ + LLVMInitialize##target##AsmPrinter(); + +#include "bolt/Core/TargetConfig.def" + } + +#define PREPARE_FUNC(name) \ + constexpr uint64_t FunctionAddress = 0x1000; \ + BinaryFunction *BF = BC->createBinaryFunction( \ + name, *TextSection, FunctionAddress, /*Size=*/0, /*SymbolSize=*/0, \ + /*Alignment=*/16); \ + /* Make sure the pass runs on the BF.*/ \ + BF->updateState(BinaryFunction::State::CFG); \ + BF->setContainedNegateRAState(); \ + /* All tests need at least one BB. */ \ + BinaryBasicBlock *BB = BF->addBasicBlock(); \ + BF->addEntryPoint(*BB); \ + BB->setCFIState(0); + + void prepareElf() { + memcpy(ElfBuf, "\177ELF", 4); + ELF64LE::Ehdr *EHdr = reinterpret_cast(ElfBuf); + EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64; + EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB; + EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64; + MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF"); + ObjFile = cantFail(ObjectFile::createObjectFile(Source)); + } + void initializeBolt() { + Relocation::Arch = ObjFile->makeTriple().getArch(); + BC = cantFail(BinaryContext::createBinaryContext( + ObjFile->makeTriple(), std::make_shared(), + ObjFile->getFileName(), nullptr, true, DWARFContext::create(*ObjFile), + {llvm::outs(), llvm::errs()})); + ASSERT_FALSE(!BC); + BC->initializeTarget(std::unique_ptr( + createMCPlusBuilder(GetParam(), BC->MIA.get(), BC->MII.get(), + BC->MRI.get(), BC->STI.get()))); + + PassManager = std::make_unique(*BC); + PassManager->registerPass( + std::make_unique(opts::PrintPAuthCFIAnalyzer)); + + TextSection = &BC->registerOrUpdateSection( + ".text", ELF::SHT_PROGBITS, ELF::SHF_ALLOC | ELF::SHF_EXECINSTR, + /*Data=*/nullptr, /*Size=*/0, + /*Alignment=*/16); + } + + std::vector findCFIOffsets(BinaryFunction &BF) { + std::vector Locations; + int Idx = 0; + int InstSize = 4; // AArch64 + for (BinaryBasicBlock &BB : BF) { + for (MCInst &Inst : BB) { + if (BC->MIB->isCFI(Inst)) { + const MCCFIInstruction *CFI = BF.getCFIFor(Inst); + if (CFI->getOperation() == MCCFIInstruction::OpNegateRAState) + Locations.push_back(Idx * InstSize); + } + Idx++; + } + } + return Locations; + } + + char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {}; + std::unique_ptr ObjFile; + std::unique_ptr BC; + std::unique_ptr PassManager; + BinarySection *TextSection; +}; +} // namespace + +TEST_P(PassTester, ExampleTest) { + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("ExampleFunction"); + + MCInst UnsignedInst = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(0) + .addImm(0); + BC->MIB->setRAState(UnsignedInst, false); + BB->addInstruction(UnsignedInst); + + MCInst SignedInst = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(1) + .addImm(0); + BC->MIB->setRAState(SignedInst, true); + BB->addInstruction(SignedInst); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + /* Expected layout of BF after the pass: + + .LBB0 (3 instructions, align : 1) + Entry Point + CFI State : 0 + 00000000: adds x0, x0, #0x0 + 00000004: !CFI $0 ; OpNegateRAState + 00000004: adds x0, x0, #0x1 + CFI State: 0 + */ + auto CFILoc = findCFIOffsets(*BF); + EXPECT_EQ(CFILoc.size(), 1u); + EXPECT_EQ(CFILoc[0], 4); +} + +TEST_P(PassTester, fillUnknownStateInBBTest) { + /* Check that a if BB starts with unknown RAState, we can fill the unknown + states based on following instructions with known RAStates. + * + * .LBB0 (1 instructions, align : 1) + Entry Point + CFI State : 0 + 00000000: adds x0, x0, #0x0 + CFI State: 0 + + .LBB1 (4 instructions, align : 1) + CFI State : 0 + 00000004: !CFI $0 ; OpNegateRAState + 00000004: adds x0, x0, #0x1 + 00000008: adds x0, x0, #0x2 + 0000000c: adds x0, x0, #0x3 + CFI State: 0 + */ + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("FuncWithUnknownStateInBB"); + BinaryBasicBlock *BB2 = BF->addBasicBlock(); + BB2->setCFIState(0); + + MCInst Unsigned = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(0) + .addImm(0); + BC->MIB->setRAState(Unsigned, false); + BB->addInstruction(Unsigned); + + MCInst Unknown = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(1) + .addImm(0); + MCInst Unknown1 = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(2) + .addImm(0); + MCInst Signed = MCInstBuilder(AArch64::ADDSXri) + .addReg(AArch64::X0) + .addReg(AArch64::X0) + .addImm(3) + .addImm(0); + BC->MIB->setRAState(Signed, true); + BB2->addInstruction(Unknown); + BB2->addInstruction(Unknown1); + BB2->addInstruction(Signed); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + auto CFILoc = findCFIOffsets(*BF); + EXPECT_EQ(CFILoc.size(), 1u); + EXPECT_EQ(CFILoc[0], 4); + // Check that the pass set Unknown and Unknown1 to signed. + // begin() is the CFI, begin() + 1 is Unknown, begin() + 2 is Unknown1. + std::optional RAState = BC->MIB->getRAState(*(BB2->begin() + 1)); + EXPECT_TRUE(RAState.has_value()); + EXPECT_TRUE(*RAState); + std::optional RAState1 = BC->MIB->getRAState(*(BB2->begin() + 2)); + EXPECT_TRUE(RAState1.has_value()); + EXPECT_TRUE(*RAState1); +} + +TEST_P(PassTester, fillUnknownStubs) { + /* + * Stubs that are not part of the function's CFG should inherit the RAState of + the BasicBlock before it. + * + * LBB1 is not part of the CFG: LBB0 jumps unconditionally to LBB2. + * LBB1 would be a stub inserted in LongJmp in real code. + * We do not add any NegateRAState CFIs, as other CFIs are not added either. + * See issue #160989 for more details. + * + * .LBB0 (1 instructions, align : 1) + Entry Point + 00000000: b .LBB2 + Successors: .LBB2 + + .LBB1 (1 instructions, align : 1) + 00000004: ret + + .LBB2 (1 instructions, align : 1) + Predecessors: .LBB0 + 00000008: ret + */ + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("FuncWithStub"); + BinaryBasicBlock *BB2 = BF->addBasicBlock(); + BB2->setCFIState(0); + BinaryBasicBlock *BB3 = BF->addBasicBlock(); + BB3->setCFIState(0); + + BB->addSuccessor(BB3); + + // Jumping over BB2, to BB3. + MCInst Jump; + BC->MIB->createUncondBranch(Jump, BB3->getLabel(), BC->Ctx.get()); + BB->addInstruction(Jump); + BC->MIB->setRAState(Jump, false); + + // BB2, in real code it would be a ShortJmp. + // Unknown RAState. + MCInst StubInst; + BC->MIB->createReturn(StubInst); + BB2->addInstruction(StubInst); + + // Can be any instruction. + MCInst Ret; + BC->MIB->createReturn(Ret); + BB3->addInstruction(Ret); + BC->MIB->setRAState(Ret, false); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + // Check that we did not generate any NegateRAState CFIs. + auto CFILoc = findCFIOffsets(*BF); + EXPECT_EQ(CFILoc.size(), 0u); +} + +TEST_P(PassTester, fillUnknownStubsEmpty) { + /* + * This test checks that BOLT can set the RAState of unknown BBs, + * even if all previous BBs are empty, hence no PrevInst gets set. + * + * As this means that the current (empty) BB is the first with non-pseudo + * instructions, the function's initialRAState should be used. + */ + if (GetParam() != Triple::aarch64) + GTEST_SKIP(); + + ASSERT_NE(TextSection, nullptr); + + PREPARE_FUNC("FuncWithStub"); + BF->setInitialRAState(false); + BinaryBasicBlock *BB2 = BF->addBasicBlock(); + BB2->setCFIState(0); + + // BB is empty. + BB->addSuccessor(BB2); + + // BB2, in real code it would be a ShortJmp. + // Unknown RAState. + MCInst StubInst; + BC->MIB->createReturn(StubInst); + BB2->addInstruction(StubInst); + + Error E = PassManager->runPasses(); + EXPECT_FALSE(E); + + // Check that BOLT added an RAState to BB2. + std::optional RAState = BC->MIB->getRAState(*(BB2->begin())); + EXPECT_TRUE(RAState.has_value()); + // BB2 should be set to BF.initialRAState (false). + EXPECT_FALSE(*RAState); +} + +#ifdef AARCH64_AVAILABLE +INSTANTIATE_TEST_SUITE_P(AArch64, PassTester, + ::testing::Values(Triple::aarch64)); +#endif diff --git a/clang-tools-extra/Maintainers.rst b/clang-tools-extra/Maintainers.rst index 2603ebadf529..094f1157f4db 100644 --- a/clang-tools-extra/Maintainers.rst +++ b/clang-tools-extra/Maintainers.rst @@ -33,6 +33,9 @@ clang-tidy | Piotr Zegar | me@piotrzegar.pl (email), PiotrZSL (GitHub), PiotrZSL (Discourse), PiotrZSL (Discord) +| Victor Baranov +| bar.victor.2002@gmail.com (email), vbvictor (GitHub), vbvictor (Discourse), vbvictor (Discord) + clang-query ----------- diff --git a/clang-tools-extra/clang-doc/BitcodeReader.cpp b/clang-tools-extra/clang-doc/BitcodeReader.cpp index dd5bbcd99878..817981aa0d4a 100644 --- a/clang-tools-extra/clang-doc/BitcodeReader.cpp +++ b/clang-tools-extra/clang-doc/BitcodeReader.cpp @@ -8,6 +8,7 @@ #include "BitcodeReader.h" #include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" #include @@ -15,6 +16,8 @@ namespace clang { namespace doc { +static llvm::ExitOnError ExitOnErr("clang-doc error: "); + using Record = llvm::SmallVector; // This implements decode for SmallString. @@ -716,8 +719,8 @@ llvm::Error addReference(FriendInfo *Friend, Reference &&R, FieldId F) { template static void addChild(T I, ChildInfoType &&R) { - llvm::errs() << "invalid child type for info"; - exit(1); + ExitOnErr(llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid child type for info")); } // Namespace children: @@ -766,8 +769,9 @@ template <> void addChild(BaseRecordInfo *I, FunctionInfo &&R) { // parameters) or TemplateSpecializationInfo (for the specialization's // parameters). template static void addTemplateParam(T I, TemplateParamInfo &&P) { - llvm::errs() << "invalid container for template parameter"; - exit(1); + ExitOnErr( + llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid container for template parameter")); } template <> void addTemplateParam(TemplateInfo *I, TemplateParamInfo &&P) { I->Params.emplace_back(std::move(P)); @@ -779,8 +783,8 @@ void addTemplateParam(TemplateSpecializationInfo *I, TemplateParamInfo &&P) { // Template info. These apply to either records or functions. template static void addTemplate(T I, TemplateInfo &&P) { - llvm::errs() << "invalid container for template info"; - exit(1); + ExitOnErr(llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid container for template info")); } template <> void addTemplate(RecordInfo *I, TemplateInfo &&P) { I->Template.emplace(std::move(P)); @@ -798,8 +802,9 @@ template <> void addTemplate(FriendInfo *I, TemplateInfo &&P) { // Template specializations go only into template records. template static void addTemplateSpecialization(T I, TemplateSpecializationInfo &&TSI) { - llvm::errs() << "invalid container for template specialization info"; - exit(1); + ExitOnErr(llvm::createStringError( + llvm::inconvertibleErrorCode(), + "invalid container for template specialization info")); } template <> void addTemplateSpecialization(TemplateInfo *I, @@ -808,8 +813,8 @@ void addTemplateSpecialization(TemplateInfo *I, } template static void addConstraint(T I, ConstraintInfo &&C) { - llvm::errs() << "invalid container for constraint info"; - exit(1); + ExitOnErr(llvm::createStringError(llvm::inconvertibleErrorCode(), + "invalid container for constraint info")); } template <> void addConstraint(TemplateInfo *I, ConstraintInfo &&C) { I->Constraints.emplace_back(std::move(C)); @@ -846,9 +851,11 @@ llvm::Error ClangDocBitcodeReader::readBlock(unsigned ID, T I) { while (true) { unsigned BlockOrCode = 0; - Cursor Res = skipUntilRecordOrBlock(BlockOrCode); + llvm::Expected C = skipUntilRecordOrBlock(BlockOrCode); + if (!C) + return C.takeError(); - switch (Res) { + switch (*C) { case Cursor::BadBlock: return llvm::createStringError(llvm::inconvertibleErrorCode(), "bad block found"); @@ -984,45 +991,39 @@ llvm::Error ClangDocBitcodeReader::readSubBlock(unsigned ID, T I) { } } -ClangDocBitcodeReader::Cursor +llvm::Expected ClangDocBitcodeReader::skipUntilRecordOrBlock(unsigned &BlockOrRecordID) { llvm::TimeTraceScope("Reducing infos", "skipUntilRecordOrBlock"); BlockOrRecordID = 0; while (!Stream.AtEndOfStream()) { - Expected MaybeCode = Stream.ReadCode(); - if (!MaybeCode) { - // FIXME this drops the error on the floor. - consumeError(MaybeCode.takeError()); - return Cursor::BadBlock; - } + Expected Code = Stream.ReadCode(); + if (!Code) + return Code.takeError(); - unsigned Code = MaybeCode.get(); - if (Code >= static_cast(llvm::bitc::FIRST_APPLICATION_ABBREV)) { - BlockOrRecordID = Code; + if (*Code >= static_cast(llvm::bitc::FIRST_APPLICATION_ABBREV)) { + BlockOrRecordID = *Code; return Cursor::Record; } - switch (static_cast(Code)) { + switch (static_cast(*Code)) { case llvm::bitc::ENTER_SUBBLOCK: if (Expected MaybeID = Stream.ReadSubBlockID()) BlockOrRecordID = MaybeID.get(); - else { - // FIXME this drops the error on the floor. - consumeError(MaybeID.takeError()); - } + else + return MaybeID.takeError(); return Cursor::BlockBegin; case llvm::bitc::END_BLOCK: if (Stream.ReadBlockEnd()) - return Cursor::BadBlock; + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "error at end of block"); return Cursor::BlockEnd; case llvm::bitc::DEFINE_ABBREV: - if (llvm::Error Err = Stream.ReadAbbrevRecord()) { - // FIXME this drops the error on the floor. - consumeError(std::move(Err)); - } + if (llvm::Error Err = Stream.ReadAbbrevRecord()) + return std::move(Err); continue; case llvm::bitc::UNABBREV_RECORD: - return Cursor::BadBlock; + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "found unabbreviated record"); case llvm::bitc::FIRST_APPLICATION_ABBREV: llvm_unreachable("Unexpected abbrev id."); } @@ -1148,10 +1149,8 @@ ClangDocBitcodeReader::readBitcode() { return std::move(Err); continue; default: - if (llvm::Error Err = Stream.SkipBlock()) { - // FIXME this drops the error on the floor. - consumeError(std::move(Err)); - } + if (llvm::Error Err = Stream.SkipBlock()) + return std::move(Err); continue; } } diff --git a/clang-tools-extra/clang-doc/BitcodeReader.h b/clang-tools-extra/clang-doc/BitcodeReader.h index d51ac8bb8331..8c1f9d67ac53 100644 --- a/clang-tools-extra/clang-doc/BitcodeReader.h +++ b/clang-tools-extra/clang-doc/BitcodeReader.h @@ -27,7 +27,8 @@ namespace doc { // Class to read bitstream into an InfoSet collection class ClangDocBitcodeReader { public: - ClangDocBitcodeReader(llvm::BitstreamCursor &Stream) : Stream(Stream) {} + ClangDocBitcodeReader(llvm::BitstreamCursor &Stream, DiagnosticsEngine &Diags) + : Stream(Stream), Diags(Diags) {} // Main entry point, calls readBlock to read each block in the given stream. llvm::Expected>> readBitcode(); @@ -57,7 +58,7 @@ class ClangDocBitcodeReader { // Helper function to step through blocks to find and dispatch the next record // or block to be read. - Cursor skipUntilRecordOrBlock(unsigned &BlockOrRecordID); + llvm::Expected skipUntilRecordOrBlock(unsigned &BlockOrRecordID); // Helper function to set up the appropriate type of Info. llvm::Expected> readBlockToInfo(unsigned ID); @@ -72,6 +73,7 @@ class ClangDocBitcodeReader { llvm::BitstreamCursor &Stream; std::optional BlockInfo; FieldId CurrentReferenceField; + DiagnosticsEngine &Diags; }; } // namespace doc diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.cpp b/clang-tools-extra/clang-doc/BitcodeWriter.cpp index 3a7ac6e2abcd..650501d1d760 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.cpp +++ b/clang-tools-extra/clang-doc/BitcodeWriter.cpp @@ -769,7 +769,9 @@ bool ClangDocBitcodeWriter::dispatchInfoForWrite(Info *I) { emitBlock(*static_cast(I)); break; case InfoType::IT_default: - llvm::errs() << "Unexpected info, unable to write.\n"; + unsigned ID = Diags.getCustomDiagID(DiagnosticsEngine::Error, + "Unexpected info, unable to write."); + Diags.Report(ID); return true; } return false; diff --git a/clang-tools-extra/clang-doc/BitcodeWriter.h b/clang-tools-extra/clang-doc/BitcodeWriter.h index 9c1b6abe983b..6d1b9e9a7ebf 100644 --- a/clang-tools-extra/clang-doc/BitcodeWriter.h +++ b/clang-tools-extra/clang-doc/BitcodeWriter.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_BITCODEWRITER_H #include "Representation.h" +#include "clang/Basic/Diagnostic.h" #include "llvm/ADT/DenseMap.h" #include "llvm/Bitstream/BitstreamWriter.h" #include @@ -175,7 +176,8 @@ enum class FieldId { class ClangDocBitcodeWriter { public: - ClangDocBitcodeWriter(llvm::BitstreamWriter &Stream) : Stream(Stream) { + ClangDocBitcodeWriter(llvm::BitstreamWriter &Stream, DiagnosticsEngine &Diags) + : Stream(Stream), Diags(Diags) { emitHeader(); emitBlockInfoBlock(); emitVersionBlock(); @@ -260,6 +262,7 @@ class ClangDocBitcodeWriter { SmallVector Record; llvm::BitstreamWriter &Stream; AbbreviationMap Abbrevs; + DiagnosticsEngine &Diags; }; } // namespace doc diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt index 5989e5fe60cf..7a375d7cd052 100644 --- a/clang-tools-extra/clang-doc/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/CMakeLists.txt @@ -16,7 +16,6 @@ add_clang_library(clangDoc STATIC Representation.cpp Serialize.cpp YAMLGenerator.cpp - HTMLMustacheGenerator.cpp JSONGenerator.cpp DEPENDS diff --git a/clang-tools-extra/clang-doc/Generators.cpp b/clang-tools-extra/clang-doc/Generators.cpp index 667e5d5a318f..d6c1cc948ce3 100644 --- a/clang-tools-extra/clang-doc/Generators.cpp +++ b/clang-tools-extra/clang-doc/Generators.cpp @@ -84,27 +84,33 @@ Error MustacheGenerator::generateDocumentation( return JSONGenerator.takeError(); } - SmallString<128> JSONPath; - sys::path::native(RootDir.str() + "/json", JSONPath); + SmallString<128> JSONDirPath(RootDir); + SmallString<128> DocsDirPath(RootDir); + { + TimeTraceScope TS("Create Output Directories"); + sys::path::append(JSONDirPath, "json"); + if (auto EC = sys::fs::create_directories(JSONDirPath)) + return createFileError(JSONDirPath, EC); + sys::path::append(DocsDirPath, DirName); + if (auto EC = sys::fs::create_directories(DocsDirPath)) + return createFileError(DocsDirPath, EC); + } { llvm::TimeTraceScope TS("Iterate JSON files"); std::error_code EC; - sys::fs::recursive_directory_iterator JSONIter(JSONPath, EC); + sys::fs::recursive_directory_iterator JSONIter(JSONDirPath, EC); std::vector JSONFiles; JSONFiles.reserve(Infos.size()); if (EC) return createStringError("Failed to create directory iterator."); - SmallString<128> DocsDirPath(RootDir.str() + '/' + DirName); - sys::path::native(DocsDirPath); - if (auto EC = sys::fs::create_directories(DocsDirPath)) - return createFileError(DocsDirPath, EC); while (JSONIter != sys::fs::recursive_directory_iterator()) { // create the same directory structure in the docs format dir if (JSONIter->type() == sys::fs::file_type::directory_file) { SmallString<128> DocsClonedPath(JSONIter->path()); - sys::path::replace_path_prefix(DocsClonedPath, JSONPath, DocsDirPath); + sys::path::replace_path_prefix(DocsClonedPath, JSONDirPath, + DocsDirPath); if (auto EC = sys::fs::create_directories(DocsClonedPath)) { return createFileError(DocsClonedPath, EC); } @@ -121,10 +127,11 @@ Error MustacheGenerator::generateDocumentation( auto File = MemoryBuffer::getFile(Path); if (EC = File.getError(); EC) { - // TODO: Buffer errors to report later, look into using Clang - // diagnostics. - llvm::errs() << "Failed to open file: " << Path << " " << EC.message() - << '\n'; + unsigned ID = CDCtx.Diags.getCustomDiagID(DiagnosticsEngine::Warning, + "Failed to open file: %0 %1"); + CDCtx.Diags.Report(ID) << Path << EC.message(); + JSONIter.increment(EC); + continue; } auto Parsed = json::parse((*File)->getBuffer()); @@ -134,7 +141,7 @@ Error MustacheGenerator::generateDocumentation( std::error_code FileErr; SmallString<128> DocsFilePath(JSONIter->path()); - sys::path::replace_path_prefix(DocsFilePath, JSONPath, DocsDirPath); + sys::path::replace_path_prefix(DocsFilePath, JSONDirPath, DocsDirPath); sys::path::replace_extension(DocsFilePath, DirName); raw_fd_ostream InfoOS(DocsFilePath, FileErr, sys::fs::OF_None); if (FileErr) @@ -236,8 +243,6 @@ void Generator::addInfoToIndex(Index &Idx, const doc::Info *Info) { [[maybe_unused]] static int YAMLGeneratorAnchorDest = YAMLGeneratorAnchorSource; [[maybe_unused]] static int MDGeneratorAnchorDest = MDGeneratorAnchorSource; [[maybe_unused]] static int HTMLGeneratorAnchorDest = HTMLGeneratorAnchorSource; -[[maybe_unused]] static int MHTMLGeneratorAnchorDest = - MHTMLGeneratorAnchorSource; [[maybe_unused]] static int JSONGeneratorAnchorDest = JSONGeneratorAnchorSource; } // namespace doc } // namespace clang diff --git a/clang-tools-extra/clang-doc/Generators.h b/clang-tools-extra/clang-doc/Generators.h index 847722646b02..a50f1ac25eda 100644 --- a/clang-tools-extra/clang-doc/Generators.h +++ b/clang-tools-extra/clang-doc/Generators.h @@ -137,7 +137,6 @@ struct MustacheGenerator : public Generator { extern volatile int YAMLGeneratorAnchorSource; extern volatile int MDGeneratorAnchorSource; extern volatile int HTMLGeneratorAnchorSource; -extern volatile int MHTMLGeneratorAnchorSource; extern volatile int JSONGeneratorAnchorSource; } // namespace doc diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 7c8c16b8e8ac..77b287476423 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -5,1145 +5,175 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the implementation of the HTMLGenerator class, +/// which is a Clang-Doc generator for HTML using Mustache templates. +/// +//===----------------------------------------------------------------------===// #include "Generators.h" #include "Representation.h" #include "support/File.h" -#include "clang/Basic/Version.h" -#include "llvm/ADT/StringExtras.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/ADT/StringSet.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/JSON.h" +#include "llvm/Support/Error.h" #include "llvm/Support/Path.h" -#include "llvm/Support/raw_ostream.h" -#include -#include -#include using namespace llvm; +using namespace llvm::json; +using namespace llvm::mustache; namespace clang { namespace doc { -namespace { - -class HTMLTag { -public: - // Any other tag can be added if required - enum TagType { - TAG_A, - TAG_DIV, - TAG_FOOTER, - TAG_H1, - TAG_H2, - TAG_H3, - TAG_HEADER, - TAG_LI, - TAG_LINK, - TAG_MAIN, - TAG_META, - TAG_OL, - TAG_P, - TAG_SCRIPT, - TAG_SPAN, - TAG_TITLE, - TAG_UL, - TAG_TABLE, - TAG_THEAD, - TAG_TBODY, - TAG_TR, - TAG_TD, - TAG_TH - }; - - HTMLTag() = default; - constexpr HTMLTag(TagType Value) : Value(Value) {} - - operator TagType() const { return Value; } - operator bool() = delete; - - bool isSelfClosing() const; - StringRef toString() const; - -private: - TagType Value; -}; - -enum NodeType { - NODE_TEXT, - NODE_TAG, -}; - -struct HTMLNode { - HTMLNode(NodeType Type) : Type(Type) {} - virtual ~HTMLNode() = default; - - virtual void render(llvm::raw_ostream &OS, int IndentationLevel) = 0; - NodeType Type; // Type of node -}; - -struct TextNode : public HTMLNode { - TextNode(const Twine &Text) - : HTMLNode(NodeType::NODE_TEXT), Text(Text.str()) {} - - std::string Text; // Content of node - void render(llvm::raw_ostream &OS, int IndentationLevel) override; -}; - -struct TagNode : public HTMLNode { - TagNode(HTMLTag Tag) : HTMLNode(NodeType::NODE_TAG), Tag(Tag) {} - TagNode(HTMLTag Tag, const Twine &Text) : TagNode(Tag) { - Children.emplace_back(std::make_unique(Text.str())); - } - - HTMLTag Tag; // Name of HTML Tag (p, div, h1) - std::vector> Children; // List of child nodes - std::vector> - Attributes; // List of key-value attributes for tag - - void render(llvm::raw_ostream &OS, int IndentationLevel) override; -}; - -struct HTMLFile { - std::vector> Children; // List of child nodes - void render(llvm::raw_ostream &OS) { - OS << "\n"; - for (const auto &C : Children) { - C->render(OS, 0); - OS << "\n"; - } - } -}; - -} // namespace - -bool HTMLTag::isSelfClosing() const { - switch (Value) { - case HTMLTag::TAG_META: - case HTMLTag::TAG_LINK: - return true; - case HTMLTag::TAG_A: - case HTMLTag::TAG_DIV: - case HTMLTag::TAG_FOOTER: - case HTMLTag::TAG_H1: - case HTMLTag::TAG_H2: - case HTMLTag::TAG_H3: - case HTMLTag::TAG_HEADER: - case HTMLTag::TAG_LI: - case HTMLTag::TAG_MAIN: - case HTMLTag::TAG_OL: - case HTMLTag::TAG_P: - case HTMLTag::TAG_SCRIPT: - case HTMLTag::TAG_SPAN: - case HTMLTag::TAG_TITLE: - case HTMLTag::TAG_UL: - case HTMLTag::TAG_TABLE: - case HTMLTag::TAG_THEAD: - case HTMLTag::TAG_TBODY: - case HTMLTag::TAG_TR: - case HTMLTag::TAG_TD: - case HTMLTag::TAG_TH: - return false; - } - llvm_unreachable("Unhandled HTMLTag::TagType"); -} - -StringRef HTMLTag::toString() const { - switch (Value) { - case HTMLTag::TAG_A: - return "a"; - case HTMLTag::TAG_DIV: - return "div"; - case HTMLTag::TAG_FOOTER: - return "footer"; - case HTMLTag::TAG_H1: - return "h1"; - case HTMLTag::TAG_H2: - return "h2"; - case HTMLTag::TAG_H3: - return "h3"; - case HTMLTag::TAG_HEADER: - return "header"; - case HTMLTag::TAG_LI: - return "li"; - case HTMLTag::TAG_LINK: - return "link"; - case HTMLTag::TAG_MAIN: - return "main"; - case HTMLTag::TAG_META: - return "meta"; - case HTMLTag::TAG_OL: - return "ol"; - case HTMLTag::TAG_P: - return "p"; - case HTMLTag::TAG_SCRIPT: - return "script"; - case HTMLTag::TAG_SPAN: - return "span"; - case HTMLTag::TAG_TITLE: - return "title"; - case HTMLTag::TAG_UL: - return "ul"; - case HTMLTag::TAG_TABLE: - return "table"; - case HTMLTag::TAG_THEAD: - return "thead"; - case HTMLTag::TAG_TBODY: - return "tbody"; - case HTMLTag::TAG_TR: - return "tr"; - case HTMLTag::TAG_TD: - return "td"; - case HTMLTag::TAG_TH: - return "th"; - } - llvm_unreachable("Unhandled HTMLTag::TagType"); -} - -void TextNode::render(llvm::raw_ostream &OS, int IndentationLevel) { - OS.indent(IndentationLevel * 2); - printHTMLEscaped(Text, OS); -} - -void TagNode::render(llvm::raw_ostream &OS, int IndentationLevel) { - // Children nodes are rendered in the same line if all of them are text nodes - bool InlineChildren = true; - for (const auto &C : Children) - if (C->Type == NodeType::NODE_TAG) { - InlineChildren = false; - break; - } - OS.indent(IndentationLevel * 2); - OS << "<" << Tag.toString(); - for (const auto &A : Attributes) - OS << " " << A.first << "=\"" << A.second << "\""; - if (Tag.isSelfClosing()) { - OS << "/>"; - return; - } - OS << ">"; - if (!InlineChildren) - OS << "\n"; - bool NewLineRendered = true; - for (const auto &C : Children) { - int ChildrenIndentation = - InlineChildren || !NewLineRendered ? 0 : IndentationLevel + 1; - C->render(OS, ChildrenIndentation); - if (!InlineChildren && (C == Children.back() || - (C->Type != NodeType::NODE_TEXT || - (&C + 1)->get()->Type != NodeType::NODE_TEXT))) { - OS << "\n"; - NewLineRendered = true; - } else - NewLineRendered = false; - } - if (!InlineChildren) - OS.indent(IndentationLevel * 2); - OS << ""; -} - -template ::value>> -static void appendVector(std::vector &&New, - std::vector &Original) { - std::move(New.begin(), New.end(), std::back_inserter(Original)); -} - -// HTML generation - -static std::vector> -genStylesheetsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) { - std::vector> Out; - for (const auto &FilePath : CDCtx.UserStylesheets) { - auto LinkNode = std::make_unique(HTMLTag::TAG_LINK); - LinkNode->Attributes.emplace_back("rel", "stylesheet"); - SmallString<128> StylesheetPath = computeRelativePath("", InfoPath); - llvm::sys::path::append(StylesheetPath, - llvm::sys::path::filename(FilePath)); - // Paths in HTML must be in posix-style - llvm::sys::path::native(StylesheetPath, llvm::sys::path::Style::posix); - LinkNode->Attributes.emplace_back("href", std::string(StylesheetPath)); - Out.emplace_back(std::move(LinkNode)); - } - return Out; -} - -static std::vector> -genJsScriptsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) { - std::vector> Out; - - // index_json.js is part of every generated HTML file - SmallString<128> IndexJSONPath = computeRelativePath("", InfoPath); - auto IndexJSONNode = std::make_unique(HTMLTag::TAG_SCRIPT); - llvm::sys::path::append(IndexJSONPath, "index_json.js"); - llvm::sys::path::native(IndexJSONPath, llvm::sys::path::Style::posix); - IndexJSONNode->Attributes.emplace_back("src", std::string(IndexJSONPath)); - Out.emplace_back(std::move(IndexJSONNode)); - - for (const auto &FilePath : CDCtx.JsScripts) { - SmallString<128> ScriptPath = computeRelativePath("", InfoPath); - auto ScriptNode = std::make_unique(HTMLTag::TAG_SCRIPT); - llvm::sys::path::append(ScriptPath, llvm::sys::path::filename(FilePath)); - // Paths in HTML must be in posix-style - llvm::sys::path::native(ScriptPath, llvm::sys::path::Style::posix); - ScriptNode->Attributes.emplace_back("src", std::string(ScriptPath)); - Out.emplace_back(std::move(ScriptNode)); - } - return Out; -} - -static std::unique_ptr genLink(const Twine &Text, const Twine &Link) { - auto LinkNode = std::make_unique(HTMLTag::TAG_A, Text); - LinkNode->Attributes.emplace_back("href", Link.str()); - return LinkNode; -} - -static std::unique_ptr -genReference(const Reference &Type, StringRef CurrentDirectory, - std::optional JumpToSection = std::nullopt) { - if (Type.Path.empty()) { - if (!JumpToSection) - return std::make_unique(Type.Name); - return genLink(Type.Name, "#" + *JumpToSection); - } - llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentDirectory); - llvm::sys::path::append(Path, Type.getFileBaseName() + ".html"); - - // Paths in HTML must be in posix-style - llvm::sys::path::native(Path, llvm::sys::path::Style::posix); - if (JumpToSection) - Path += ("#" + *JumpToSection).str(); - return genLink(Type.Name, Path); -} - -static std::vector> -genReferenceList(const llvm::SmallVectorImpl &Refs, - const StringRef &CurrentDirectory) { - std::vector> Out; - for (const auto &R : Refs) { - if (&R != Refs.begin()) - Out.emplace_back(std::make_unique(", ")); - Out.emplace_back(genReference(R, CurrentDirectory)); - } - return Out; -} - -static std::vector> -genHTML(const EnumInfo &I, const ClangDocContext &CDCtx); -static std::vector> -genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, - StringRef ParentInfoDir); -static std::unique_ptr genHTML(const std::vector &C); - -static std::vector> -genEnumsBlock(const std::vector &Enums, - const ClangDocContext &CDCtx) { - if (Enums.empty()) - return {}; - - std::vector> Out; - Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, "Enums")); - Out.back()->Attributes.emplace_back("id", "Enums"); - Out.emplace_back(std::make_unique(HTMLTag::TAG_DIV)); - auto &DivBody = Out.back(); - for (const auto &E : Enums) { - std::vector> Nodes = genHTML(E, CDCtx); - appendVector(std::move(Nodes), DivBody->Children); - } - return Out; -} - -static std::unique_ptr -genEnumMembersBlock(const llvm::SmallVector &Members) { - if (Members.empty()) - return nullptr; - - auto List = std::make_unique(HTMLTag::TAG_TBODY); - - for (const auto &M : Members) { - auto TRNode = std::make_unique(HTMLTag::TAG_TR); - TRNode->Children.emplace_back( - std::make_unique(HTMLTag::TAG_TD, M.Name)); - // Use user supplied value if it exists, otherwise use the value - if (!M.ValueExpr.empty()) { - TRNode->Children.emplace_back( - std::make_unique(HTMLTag::TAG_TD, M.ValueExpr)); - } else { - TRNode->Children.emplace_back( - std::make_unique(HTMLTag::TAG_TD, M.Value)); - } - if (!M.Description.empty()) { - auto TD = std::make_unique(HTMLTag::TAG_TD); - TD->Children.emplace_back(genHTML(M.Description)); - TRNode->Children.emplace_back(std::move(TD)); - } - List->Children.emplace_back(std::move(TRNode)); - } - return List; -} - -static std::vector> -genFunctionsBlock(const std::vector &Functions, - const ClangDocContext &CDCtx, StringRef ParentInfoDir) { - if (Functions.empty()) - return {}; - - std::vector> Out; - Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, "Functions")); - Out.back()->Attributes.emplace_back("id", "Functions"); - Out.emplace_back(std::make_unique(HTMLTag::TAG_DIV)); - auto &DivBody = Out.back(); - for (const auto &F : Functions) { - std::vector> Nodes = - genHTML(F, CDCtx, ParentInfoDir); - appendVector(std::move(Nodes), DivBody->Children); - } - return Out; -} - -static std::vector> -genRecordMembersBlock(const llvm::SmallVector &Members, - StringRef ParentInfoDir) { - if (Members.empty()) - return {}; - - std::vector> Out; - Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, "Members")); - Out.back()->Attributes.emplace_back("id", "Members"); - Out.emplace_back(std::make_unique(HTMLTag::TAG_UL)); - auto &ULBody = Out.back(); - for (const auto &M : Members) { - StringRef Access = getAccessSpelling(M.Access); - auto LIBody = std::make_unique(HTMLTag::TAG_LI); - auto MemberDecl = std::make_unique(HTMLTag::TAG_DIV); - if (!Access.empty()) - MemberDecl->Children.emplace_back( - std::make_unique(Access + " ")); - if (M.IsStatic) - MemberDecl->Children.emplace_back(std::make_unique("static ")); - MemberDecl->Children.emplace_back(genReference(M.Type, ParentInfoDir)); - MemberDecl->Children.emplace_back(std::make_unique(" " + M.Name)); - if (!M.Description.empty()) - LIBody->Children.emplace_back(genHTML(M.Description)); - LIBody->Children.emplace_back(std::move(MemberDecl)); - ULBody->Children.emplace_back(std::move(LIBody)); - } - return Out; -} - -static std::vector> -genReferencesBlock(const std::vector &References, - llvm::StringRef Title, StringRef ParentPath) { - if (References.empty()) - return {}; - - std::vector> Out; - Out.emplace_back(std::make_unique(HTMLTag::TAG_H2, Title)); - Out.back()->Attributes.emplace_back("id", std::string(Title)); - Out.emplace_back(std::make_unique(HTMLTag::TAG_UL)); - auto &ULBody = Out.back(); - for (const auto &R : References) { - auto LiNode = std::make_unique(HTMLTag::TAG_LI); - LiNode->Children.emplace_back(genReference(R, ParentPath)); - ULBody->Children.emplace_back(std::move(LiNode)); - } - return Out; -} -static std::unique_ptr writeSourceFileRef(const ClangDocContext &CDCtx, - const Location &L) { - - if (!L.IsFileInRootDir && !CDCtx.RepositoryUrl) - return std::make_unique( - HTMLTag::TAG_P, "Defined at line " + std::to_string(L.StartLineNumber) + - " of file " + L.Filename); - - SmallString<128> FileURL(CDCtx.RepositoryUrl.value_or("")); - llvm::sys::path::append( - FileURL, llvm::sys::path::Style::posix, - // If we're on Windows, the file name will be in the wrong format, and - // append won't convert the full path being appended to the correct - // format, so we need to do that here. - llvm::sys::path::convert_to_slash( - L.Filename, - // The style here is the current style of the path, not the one we're - // targeting. If the string is already in the posix style, it will do - // nothing. - llvm::sys::path::Style::windows)); - auto Node = std::make_unique(HTMLTag::TAG_P); - Node->Children.emplace_back(std::make_unique("Defined at line ")); - auto LocNumberNode = std::make_unique( - HTMLTag::TAG_A, std::to_string(L.StartLineNumber)); - // The links to a specific line in the source code use the github / - // googlesource notation so it won't work for all hosting pages. - LocNumberNode->Attributes.emplace_back( - "href", - formatv("{0}#{1}{2}", FileURL, CDCtx.RepositoryLinePrefix.value_or(""), - L.StartLineNumber)); - Node->Children.emplace_back(std::move(LocNumberNode)); - Node->Children.emplace_back(std::make_unique(" of file ")); - auto LocFileNode = std::make_unique( - HTMLTag::TAG_A, llvm::sys::path::filename(FileURL)); - LocFileNode->Attributes.emplace_back("href", std::string(FileURL)); - Node->Children.emplace_back(std::move(LocFileNode)); - return Node; -} - -static void maybeWriteSourceFileRef(std::vector> &Out, - const ClangDocContext &CDCtx, - const std::optional &DefLoc) { - if (DefLoc) - Out.emplace_back(writeSourceFileRef(CDCtx, *DefLoc)); -} - -static std::vector> -genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList); - -// Generates a list of child nodes for the HTML head tag -// It contains a meta node, link nodes to import CSS files, and script nodes to -// import JS files -static std::vector> -genFileHeadNodes(StringRef Title, StringRef InfoPath, - const ClangDocContext &CDCtx) { - std::vector> Out; - auto MetaNode = std::make_unique(HTMLTag::TAG_META); - MetaNode->Attributes.emplace_back("charset", "utf-8"); - Out.emplace_back(std::move(MetaNode)); - Out.emplace_back(std::make_unique(HTMLTag::TAG_TITLE, Title)); - std::vector> StylesheetsNodes = - genStylesheetsHTML(InfoPath, CDCtx); - appendVector(std::move(StylesheetsNodes), Out); - std::vector> JsNodes = - genJsScriptsHTML(InfoPath, CDCtx); - appendVector(std::move(JsNodes), Out); - return Out; -} - -// Generates a header HTML node that can be used for any file -// It contains the project name -static std::unique_ptr genFileHeaderNode(StringRef ProjectName) { - auto HeaderNode = std::make_unique(HTMLTag::TAG_HEADER, ProjectName); - HeaderNode->Attributes.emplace_back("id", "project-title"); - return HeaderNode; -} - -// Generates a main HTML node that has all the main content of an info file -// It contains both indexes and the info's documented information -// This function should only be used for the info files (not for the file that -// only has the general index) -static std::unique_ptr genInfoFileMainNode( - StringRef InfoPath, - std::vector> &MainContentInnerNodes, - const Index &InfoIndex) { - auto MainNode = std::make_unique(HTMLTag::TAG_MAIN); - - auto LeftSidebarNode = std::make_unique(HTMLTag::TAG_DIV); - LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left"); - LeftSidebarNode->Attributes.emplace_back("path", std::string(InfoPath)); - LeftSidebarNode->Attributes.emplace_back( - "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"); - - auto MainContentNode = std::make_unique(HTMLTag::TAG_DIV); - MainContentNode->Attributes.emplace_back("id", "main-content"); - MainContentNode->Attributes.emplace_back( - "class", "col-xs-12 col-sm-9 col-md-8 main-content"); - appendVector(std::move(MainContentInnerNodes), MainContentNode->Children); - - auto RightSidebarNode = std::make_unique(HTMLTag::TAG_DIV); - RightSidebarNode->Attributes.emplace_back("id", "sidebar-right"); - RightSidebarNode->Attributes.emplace_back( - "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right"); - std::vector> InfoIndexHTML = - genHTML(InfoIndex, InfoPath, true); - appendVector(std::move(InfoIndexHTML), RightSidebarNode->Children); - - MainNode->Children.emplace_back(std::move(LeftSidebarNode)); - MainNode->Children.emplace_back(std::move(MainContentNode)); - MainNode->Children.emplace_back(std::move(RightSidebarNode)); - - return MainNode; -} - -// Generates a footer HTML node that can be used for any file -// It contains clang-doc's version -static std::unique_ptr genFileFooterNode() { - auto FooterNode = std::make_unique(HTMLTag::TAG_FOOTER); - auto SpanNode = std::make_unique( - HTMLTag::TAG_SPAN, clang::getClangToolFullVersion("clang-doc")); - SpanNode->Attributes.emplace_back("class", "no-break"); - FooterNode->Children.emplace_back(std::move(SpanNode)); - return FooterNode; -} - -// Generates a complete HTMLFile for an Info -static HTMLFile -genInfoFile(StringRef Title, StringRef InfoPath, - std::vector> &MainContentNodes, - const Index &InfoIndex, const ClangDocContext &CDCtx) { - HTMLFile F; - - std::vector> HeadNodes = - genFileHeadNodes(Title, InfoPath, CDCtx); - std::unique_ptr HeaderNode = genFileHeaderNode(CDCtx.ProjectName); - std::unique_ptr MainNode = - genInfoFileMainNode(InfoPath, MainContentNodes, InfoIndex); - std::unique_ptr FooterNode = genFileFooterNode(); - - appendVector(std::move(HeadNodes), F.Children); - F.Children.emplace_back(std::move(HeaderNode)); - F.Children.emplace_back(std::move(MainNode)); - F.Children.emplace_back(std::move(FooterNode)); - - return F; -} - -template ::value>> -static Index genInfoIndexItem(const std::vector &Infos, StringRef Title) { - Index Idx(Title, Title); - for (const auto &C : Infos) - Idx.Children.emplace_back(C.extractName(), - llvm::toHex(llvm::toStringRef(C.USR))); - return Idx; -} - -static std::vector> -genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList) { - std::vector> Out; - if (!Index.Name.empty()) { - Out.emplace_back(std::make_unique(HTMLTag::TAG_SPAN)); - auto &SpanBody = Out.back(); - if (!Index.JumpToSection) - SpanBody->Children.emplace_back(genReference(Index, InfoPath)); - else - SpanBody->Children.emplace_back( - genReference(Index, InfoPath, Index.JumpToSection->str())); - } - if (Index.Children.empty()) - return Out; - // Only the outermost list should use ol, the others should use ul - HTMLTag ListHTMLTag = IsOutermostList ? HTMLTag::TAG_OL : HTMLTag::TAG_UL; - Out.emplace_back(std::make_unique(ListHTMLTag)); - const auto &UlBody = Out.back(); - for (const auto &C : Index.Children) { - auto LiBody = std::make_unique(HTMLTag::TAG_LI); - std::vector> Nodes = genHTML(C, InfoPath, false); - appendVector(std::move(Nodes), LiBody->Children); - UlBody->Children.emplace_back(std::move(LiBody)); - } - return Out; -} - -static std::unique_ptr genHTML(const CommentInfo &I) { - switch (I.Kind) { - case CommentKind::CK_FullComment: { - auto FullComment = std::make_unique(HTMLTag::TAG_DIV); - for (const auto &Child : I.Children) { - std::unique_ptr Node = genHTML(*Child); - if (Node) - FullComment->Children.emplace_back(std::move(Node)); - } - return std::move(FullComment); - } - - case CommentKind::CK_ParagraphComment: { - auto ParagraphComment = std::make_unique(HTMLTag::TAG_P); - for (const auto &Child : I.Children) { - std::unique_ptr Node = genHTML(*Child); - if (Node) - ParagraphComment->Children.emplace_back(std::move(Node)); - } - if (ParagraphComment->Children.empty()) - return nullptr; - return std::move(ParagraphComment); - } - - case CommentKind::CK_BlockCommandComment: { - auto BlockComment = std::make_unique(HTMLTag::TAG_DIV); - BlockComment->Children.emplace_back( - std::make_unique(HTMLTag::TAG_DIV, I.Name)); - for (const auto &Child : I.Children) { - std::unique_ptr Node = genHTML(*Child); - if (Node) - BlockComment->Children.emplace_back(std::move(Node)); - } - if (BlockComment->Children.empty()) - return nullptr; - return std::move(BlockComment); - } - - case CommentKind::CK_TextComment: { - if (I.Text.empty()) - return nullptr; - return std::make_unique(I.Text); - } - - // For now, return nullptr for unsupported comment kinds - case CommentKind::CK_InlineCommandComment: - case CommentKind::CK_HTMLStartTagComment: - case CommentKind::CK_HTMLEndTagComment: - case CommentKind::CK_ParamCommandComment: - case CommentKind::CK_TParamCommandComment: - case CommentKind::CK_VerbatimBlockComment: - case CommentKind::CK_VerbatimBlockLineComment: - case CommentKind::CK_VerbatimLineComment: - case CommentKind::CK_Unknown: - return nullptr; - } - llvm_unreachable("Unhandled CommentKind"); -} - -static std::unique_ptr genHTML(const std::vector &C) { - auto CommentBlock = std::make_unique(HTMLTag::TAG_DIV); - for (const auto &Child : C) { - if (std::unique_ptr Node = genHTML(Child)) - CommentBlock->Children.emplace_back(std::move(Node)); - } - return CommentBlock; -} - -static std::vector> -genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) { - std::vector> Out; - std::string EnumType = I.Scoped ? "enum class " : "enum "; - // Determine if enum members have comments attached - bool HasComments = llvm::any_of( - I.Members, [](const EnumValueInfo &M) { return !M.Description.empty(); }); - std::unique_ptr Table = - std::make_unique(HTMLTag::TAG_TABLE); - std::unique_ptr THead = - std::make_unique(HTMLTag::TAG_THEAD); - std::unique_ptr TRow = std::make_unique(HTMLTag::TAG_TR); - std::unique_ptr TD = - std::make_unique(HTMLTag::TAG_TH, EnumType + I.Name); - // Span 3 columns if enum has comments - TD->Attributes.emplace_back("colspan", HasComments ? "3" : "2"); - - Table->Attributes.emplace_back("id", llvm::toHex(llvm::toStringRef(I.USR))); - TRow->Children.emplace_back(std::move(TD)); - THead->Children.emplace_back(std::move(TRow)); - Table->Children.emplace_back(std::move(THead)); - - if (std::unique_ptr Node = genEnumMembersBlock(I.Members)) - Table->Children.emplace_back(std::move(Node)); - - Out.emplace_back(std::move(Table)); - - maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc); - - if (!I.Description.empty()) - Out.emplace_back(genHTML(I.Description)); - - return Out; -} - -static std::vector> -genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx, - StringRef ParentInfoDir) { - std::vector> Out; - Out.emplace_back(std::make_unique(HTMLTag::TAG_H3, I.Name)); - // USR is used as id for functions instead of name to disambiguate function - // overloads. - Out.back()->Attributes.emplace_back("id", - llvm::toHex(llvm::toStringRef(I.USR))); - - Out.emplace_back(std::make_unique(HTMLTag::TAG_P)); - auto &FunctionHeader = Out.back(); - - std::string Access = getAccessSpelling(I.Access).str(); - if (Access != "") - FunctionHeader->Children.emplace_back( - std::make_unique(Access + " ")); - if (I.IsStatic) - FunctionHeader->Children.emplace_back( - std::make_unique("static ")); - if (I.ReturnType.Type.Name != "") { - FunctionHeader->Children.emplace_back( - genReference(I.ReturnType.Type, ParentInfoDir)); - FunctionHeader->Children.emplace_back(std::make_unique(" ")); - } - FunctionHeader->Children.emplace_back( - std::make_unique(I.Name + "(")); - - for (const auto &P : I.Params) { - if (&P != I.Params.begin()) - FunctionHeader->Children.emplace_back(std::make_unique(", ")); - FunctionHeader->Children.emplace_back(genReference(P.Type, ParentInfoDir)); - FunctionHeader->Children.emplace_back( - std::make_unique(" " + P.Name)); - } - FunctionHeader->Children.emplace_back(std::make_unique(")")); - - maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc); - - if (!I.Description.empty()) - Out.emplace_back(genHTML(I.Description)); - - return Out; -} - -static std::vector> -genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx, - std::string &InfoTitle) { - std::vector> Out; - if (I.Name.str() == "") - InfoTitle = "Global Namespace"; - else - InfoTitle = ("namespace " + I.Name).str(); +static std::unique_ptr NamespaceTemplate = nullptr; - Out.emplace_back(std::make_unique(HTMLTag::TAG_H1, InfoTitle)); +static std::unique_ptr RecordTemplate = nullptr; - if (!I.Description.empty()) - Out.emplace_back(genHTML(I.Description)); - - llvm::SmallString<64> BasePath = I.getRelativeFilePath(""); - - std::vector> ChildNamespaces = - genReferencesBlock(I.Children.Namespaces, "Namespaces", BasePath); - appendVector(std::move(ChildNamespaces), Out); - std::vector> ChildRecords = - genReferencesBlock(I.Children.Records, "Records", BasePath); - appendVector(std::move(ChildRecords), Out); - - std::vector> ChildFunctions = - genFunctionsBlock(I.Children.Functions, CDCtx, BasePath); - appendVector(std::move(ChildFunctions), Out); - std::vector> ChildEnums = - genEnumsBlock(I.Children.Enums, CDCtx); - appendVector(std::move(ChildEnums), Out); - - if (!I.Children.Namespaces.empty()) - InfoIndex.Children.emplace_back("Namespaces", "Namespaces"); - if (!I.Children.Records.empty()) - InfoIndex.Children.emplace_back("Records", "Records"); - if (!I.Children.Functions.empty()) - InfoIndex.Children.emplace_back( - genInfoIndexItem(I.Children.Functions, "Functions")); - if (!I.Children.Enums.empty()) - InfoIndex.Children.emplace_back( - genInfoIndexItem(I.Children.Enums, "Enums")); - - return Out; -} - -static std::vector> -genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx, - std::string &InfoTitle) { - std::vector> Out; - InfoTitle = (getTagType(I.TagType) + " " + I.Name).str(); - Out.emplace_back(std::make_unique(HTMLTag::TAG_H1, InfoTitle)); - - maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc); - - if (!I.Description.empty()) - Out.emplace_back(genHTML(I.Description)); - - std::vector> Parents = - genReferenceList(I.Parents, I.Path); - std::vector> VParents = - genReferenceList(I.VirtualParents, I.Path); - if (!Parents.empty() || !VParents.empty()) { - Out.emplace_back(std::make_unique(HTMLTag::TAG_P)); - auto &PBody = Out.back(); - PBody->Children.emplace_back(std::make_unique("Inherits from ")); - if (Parents.empty()) - appendVector(std::move(VParents), PBody->Children); - else if (VParents.empty()) - appendVector(std::move(Parents), PBody->Children); - else { - appendVector(std::move(Parents), PBody->Children); - PBody->Children.emplace_back(std::make_unique(", ")); - appendVector(std::move(VParents), PBody->Children); - } - } - - std::vector> Members = - genRecordMembersBlock(I.Members, I.Path); - appendVector(std::move(Members), Out); - std::vector> ChildRecords = - genReferencesBlock(I.Children.Records, "Records", I.Path); - appendVector(std::move(ChildRecords), Out); - - std::vector> ChildFunctions = - genFunctionsBlock(I.Children.Functions, CDCtx, I.Path); - appendVector(std::move(ChildFunctions), Out); - std::vector> ChildEnums = - genEnumsBlock(I.Children.Enums, CDCtx); - appendVector(std::move(ChildEnums), Out); - - if (!I.Members.empty()) - InfoIndex.Children.emplace_back("Members", "Members"); - if (!I.Children.Records.empty()) - InfoIndex.Children.emplace_back("Records", "Records"); - if (!I.Children.Functions.empty()) - InfoIndex.Children.emplace_back( - genInfoIndexItem(I.Children.Functions, "Functions")); - if (!I.Children.Enums.empty()) - InfoIndex.Children.emplace_back( - genInfoIndexItem(I.Children.Enums, "Enums")); - - return Out; -} - -static std::vector> -genHTML(const TypedefInfo &I, const ClangDocContext &CDCtx, - std::string &InfoTitle) { - // TODO support typedefs in HTML. - return {}; -} - -/// Generator for HTML documentation. -class HTMLGenerator : public Generator { +class HTMLGenerator : public MustacheGenerator { public: static const char *Format; - + Error createResources(ClangDocContext &CDCtx) override; + Error generateDocForInfo(Info *I, raw_ostream &OS, + const ClangDocContext &CDCtx) override; + Error setupTemplateFiles(const ClangDocContext &CDCtx) override; + Error generateDocForJSON(json::Value &JSON, raw_fd_ostream &OS, + const ClangDocContext &CDCtx, StringRef ObjTypeStr, + StringRef RelativeRootPath) override; + // Populates templates with CSS stylesheets, JS scripts paths. + Error setupTemplateResources(const ClangDocContext &CDCtx, json::Value &V, + SmallString<128> RelativeRootPath); llvm::Error generateDocumentation( StringRef RootDir, llvm::StringMap> Infos, const ClangDocContext &CDCtx, std::string DirName) override; - llvm::Error createResources(ClangDocContext &CDCtx) override; - llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, - const ClangDocContext &CDCtx) override; }; -const char *HTMLGenerator::Format = "html"; - -llvm::Error HTMLGenerator::generateDocumentation( - StringRef RootDir, llvm::StringMap> Infos, - const ClangDocContext &CDCtx, std::string DirName) { - // Track which directories we already tried to create. - llvm::StringSet<> CreatedDirs; +Error HTMLGenerator::setupTemplateFiles(const ClangDocContext &CDCtx) { + // Template files need to use the native path when they're opened, + // but have to be used in POSIX style when used in HTML. + auto ConvertToNative = [](std::string &&Path) -> std::string { + SmallString<128> PathBuf(Path); + llvm::sys::path::native(PathBuf); + return PathBuf.str().str(); + }; - // Collect all output by file name and create the nexessary directories. - llvm::StringMap> FileToInfos; - for (const auto &Group : Infos) { - doc::Info *Info = Group.getValue().get(); + std::string NamespaceFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("namespace-template")); + std::string ClassFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("class-template")); + std::string CommentFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("comment-template")); + std::string FunctionFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("function-template")); + std::string EnumFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("enum-template")); + std::string HeadFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("head-template")); + std::string NavbarFilePath = + ConvertToNative(CDCtx.MustacheTemplates.lookup("navbar-template")); + std::vector> Partials = { + {"Comments", CommentFilePath}, + {"FunctionPartial", FunctionFilePath}, + {"EnumPartial", EnumFilePath}, + {"HeadPartial", HeadFilePath}, + {"NavbarPartial", NavbarFilePath}}; + + if (Error Err = setupTemplate(NamespaceTemplate, NamespaceFilePath, Partials)) + return Err; - llvm::SmallString<128> Path; - llvm::sys::path::native(RootDir, Path); - llvm::sys::path::append(Path, Info->getRelativeFilePath("")); - if (!CreatedDirs.contains(Path)) { - if (std::error_code Err = llvm::sys::fs::create_directories(Path); - Err != std::error_code()) { - return llvm::createStringError(Err, "Failed to create directory '%s'.", - Path.c_str()); - } - CreatedDirs.insert(Path); - } + if (Error Err = setupTemplate(RecordTemplate, ClassFilePath, Partials)) + return Err; - llvm::sys::path::append(Path, Info->getFileBaseName() + ".html"); - FileToInfos[Path].push_back(Info); - } + return Error::success(); +} - for (const auto &Group : FileToInfos) { - std::error_code FileErr; - llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, - llvm::sys::fs::OF_Text); - if (FileErr) { - return llvm::createStringError(FileErr, "Error opening file '%s'", - Group.getKey().str().c_str()); - } +Error HTMLGenerator::setupTemplateResources(const ClangDocContext &CDCtx, + json::Value &V, + SmallString<128> RelativeRootPath) { + V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName}); + json::Value StylesheetArr = Array(); + sys::path::native(RelativeRootPath, sys::path::Style::posix); - // TODO: https://github.com/llvm/llvm-project/issues/59073 - // If there are multiple Infos for this file name (for example, template - // specializations), this will generate multiple complete web pages (with - // and , etc.) concatenated together. This generator needs - // some refactoring to be able to output the headers separately from the - // contents. - for (const auto &Info : Group.getValue()) { - if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) { - return Err; - } - } + auto *SSA = StylesheetArr.getAsArray(); + SSA->reserve(CDCtx.UserStylesheets.size()); + for (const auto &FilePath : CDCtx.UserStylesheets) { + SmallString<128> StylesheetPath = RelativeRootPath; + sys::path::append(StylesheetPath, sys::path::Style::posix, + sys::path::filename(FilePath)); + SSA->emplace_back(StylesheetPath); + } + V.getAsObject()->insert({"Stylesheets", StylesheetArr}); + + json::Value ScriptArr = Array(); + auto *SCA = ScriptArr.getAsArray(); + SCA->reserve(CDCtx.JsScripts.size()); + for (auto Script : CDCtx.JsScripts) { + SmallString<128> JsPath = RelativeRootPath; + sys::path::append(JsPath, sys::path::Style::posix, + sys::path::filename(Script)); + SCA->emplace_back(JsPath); + } + V.getAsObject()->insert({"Scripts", ScriptArr}); + return Error::success(); +} + +Error HTMLGenerator::generateDocForJSON(json::Value &JSON, raw_fd_ostream &OS, + const ClangDocContext &CDCtx, + StringRef ObjTypeStr, + StringRef RelativeRootPath) { + if (ObjTypeStr == "namespace") { + if (auto Err = setupTemplateResources(CDCtx, JSON, RelativeRootPath)) + return Err; + assert(NamespaceTemplate && "NamespaceTemplate is nullptr."); + NamespaceTemplate->render(JSON, OS); + } else if (ObjTypeStr == "record") { + if (auto Err = setupTemplateResources(CDCtx, JSON, RelativeRootPath)) + return Err; + assert(RecordTemplate && "RecordTemplate is nullptr."); + RecordTemplate->render(JSON, OS); } - - return llvm::Error::success(); + return Error::success(); } -llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, - const ClangDocContext &CDCtx) { - std::string InfoTitle; - std::vector<std::unique_ptr<TagNode>> MainContentNodes; - Index InfoIndex; +Error HTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS, + const ClangDocContext &CDCtx) { switch (I->IT) { - case InfoType::IT_namespace: - MainContentNodes = genHTML(*static_cast<clang::doc::NamespaceInfo *>(I), - InfoIndex, CDCtx, InfoTitle); - break; - case InfoType::IT_record: - MainContentNodes = genHTML(*static_cast<clang::doc::RecordInfo *>(I), - InfoIndex, CDCtx, InfoTitle); - break; case InfoType::IT_enum: - MainContentNodes = genHTML(*static_cast<clang::doc::EnumInfo *>(I), CDCtx); - break; case InfoType::IT_function: - MainContentNodes = - genHTML(*static_cast<clang::doc::FunctionInfo *>(I), CDCtx, ""); - break; case InfoType::IT_typedef: - MainContentNodes = - genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle); - break; - case InfoType::IT_concept: - case InfoType::IT_variable: - case InfoType::IT_friend: - break; - case InfoType::IT_default: - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "unexpected info type"); - } - - HTMLFile F = genInfoFile(InfoTitle, I->getRelativeFilePath(""), - MainContentNodes, InfoIndex, CDCtx); - F.render(OS); - - return llvm::Error::success(); -} - -static std::string getRefType(InfoType IT) { - switch (IT) { - case InfoType::IT_default: - return "default"; case InfoType::IT_namespace: - return "namespace"; case InfoType::IT_record: - return "record"; - case InfoType::IT_function: - return "function"; - case InfoType::IT_enum: - return "enum"; - case InfoType::IT_typedef: - return "typedef"; case InfoType::IT_concept: - return "concept"; case InfoType::IT_variable: - return "variable"; case InfoType::IT_friend: - return "friend"; - } - llvm_unreachable("Unknown InfoType"); -} - -static llvm::Error serializeIndex(ClangDocContext &CDCtx) { - std::error_code OK; - std::error_code FileErr; - llvm::SmallString<128> FilePath; - llvm::sys::path::native(CDCtx.OutDirectory, FilePath); - llvm::sys::path::append(FilePath, "index_json.js"); - llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text); - if (FileErr != OK) { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "error creating index file: " + - FileErr.message()); - } - llvm::SmallString<128> RootPath(CDCtx.OutDirectory); - if (llvm::sys::path::is_relative(RootPath)) { - llvm::sys::fs::make_absolute(RootPath); + break; + case InfoType::IT_default: + return createStringError(inconvertibleErrorCode(), "unexpected InfoType"); } - // Replace the escaped characters with a forward slash. It shouldn't matter - // when rendering the webpage in a web browser. This helps to prevent the - // JavaScript from escaping characters incorrectly, and introducing bad paths - // in the URLs. - std::string RootPathEscaped = RootPath.str().str(); - llvm::replace(RootPathEscaped, '\\', '/'); - OS << "var RootPath = \"" << RootPathEscaped << "\";\n"; - - llvm::SmallString<128> Base(CDCtx.Base); - std::string BaseEscaped = Base.str().str(); - llvm::replace(BaseEscaped, '\\', '/'); - OS << "var Base = \"" << BaseEscaped << "\";\n"; - - CDCtx.Idx.sort(); - llvm::json::OStream J(OS, 2); - std::function<void(Index)> IndexToJSON = [&](const Index &I) { - J.object([&] { - J.attribute("USR", toHex(llvm::toStringRef(I.USR))); - J.attribute("Name", I.Name); - J.attribute("RefType", getRefType(I.RefType)); - J.attribute("Path", I.getRelativeFilePath("")); - J.attributeArray("Children", [&] { - for (const Index &C : I.Children) - IndexToJSON(C); - }); - }); - }; - OS << "async function LoadIndex() {\nreturn"; - IndexToJSON(CDCtx.Idx); - OS << ";\n}"; - return llvm::Error::success(); + return Error::success(); } -// Generates a main HTML node that has the main content of the file that shows -// only the general index -// It contains the general index with links to all the generated files -static std::unique_ptr<TagNode> genIndexFileMainNode() { - auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN); - - auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV); - LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left"); - LeftSidebarNode->Attributes.emplace_back("path", ""); - LeftSidebarNode->Attributes.emplace_back( - "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left"); - LeftSidebarNode->Attributes.emplace_back("style", "flex: 0 100%;"); - - MainNode->Children.emplace_back(std::move(LeftSidebarNode)); - - return MainNode; +Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { + std::string ResourcePath(CDCtx.OutDirectory + "/html"); + for (const auto &FilePath : CDCtx.UserStylesheets) + if (Error Err = copyFile(FilePath, ResourcePath)) + return Err; + for (const auto &FilePath : CDCtx.JsScripts) + if (Error Err = copyFile(FilePath, ResourcePath)) + return Err; + return Error::success(); } -static llvm::Error genIndex(const ClangDocContext &CDCtx) { - std::error_code FileErr, OK; - llvm::SmallString<128> IndexPath; - llvm::sys::path::native(CDCtx.OutDirectory, IndexPath); - llvm::sys::path::append(IndexPath, "index.html"); - llvm::raw_fd_ostream IndexOS(IndexPath, FileErr, llvm::sys::fs::OF_Text); - if (FileErr != OK) { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "error creating main index: " + - FileErr.message()); - } - - HTMLFile F; - - std::vector<std::unique_ptr<TagNode>> HeadNodes = - genFileHeadNodes("Index", "", CDCtx); - std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName); - std::unique_ptr<TagNode> MainNode = genIndexFileMainNode(); - std::unique_ptr<TagNode> FooterNode = genFileFooterNode(); - - appendVector(std::move(HeadNodes), F.Children); - F.Children.emplace_back(std::move(HeaderNode)); - F.Children.emplace_back(std::move(MainNode)); - F.Children.emplace_back(std::move(FooterNode)); - - F.render(IndexOS); - - return llvm::Error::success(); +Error HTMLGenerator::generateDocumentation( + StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos, + const ClangDocContext &CDCtx, std::string DirName) { + return MustacheGenerator::generateDocumentation(RootDir, std::move(Infos), + CDCtx, "html"); } -llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) { - auto Err = serializeIndex(CDCtx); - if (Err) - return Err; - Err = genIndex(CDCtx); - if (Err) - return Err; - - for (const auto &FilePath : CDCtx.UserStylesheets) { - Err = copyFile(FilePath, CDCtx.OutDirectory); - if (Err) - return Err; - } - for (const auto &FilePath : CDCtx.JsScripts) { - Err = copyFile(FilePath, CDCtx.OutDirectory); - if (Err) - return Err; - } - return llvm::Error::success(); -} +const char *HTMLGenerator::Format = "html"; -static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format, - "Generator for HTML output."); +static GeneratorRegistry::Add<HTMLGenerator> + HTML(HTMLGenerator::Format, "Generator for mustache HTML output."); // This anchor is used to force the linker to link in the generated object // file and thus register the generator. diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp deleted file mode 100644 index c09f908c7eb2..000000000000 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ /dev/null @@ -1,178 +0,0 @@ -///===----------------------------------------------------------------------===// -// -// 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 -/// This file contains the implementation of the MustacheHTMLGenerator class, -/// which is Clang-Doc generator for HTML using Mustache templates. -/// -//===----------------------------------------------------------------------===// - -#include "Generators.h" -#include "Representation.h" -#include "support/File.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/Path.h" - -using namespace llvm; -using namespace llvm::json; -using namespace llvm::mustache; - -namespace clang { -namespace doc { - -static std::unique_ptr<MustacheTemplateFile> NamespaceTemplate = nullptr; - -static std::unique_ptr<MustacheTemplateFile> RecordTemplate = nullptr; - -class MustacheHTMLGenerator : public MustacheGenerator { -public: - static const char *Format; - Error createResources(ClangDocContext &CDCtx) override; - Error generateDocForInfo(Info *I, raw_ostream &OS, - const ClangDocContext &CDCtx) override; - Error setupTemplateFiles(const ClangDocContext &CDCtx) override; - Error generateDocForJSON(json::Value &JSON, raw_fd_ostream &OS, - const ClangDocContext &CDCtx, StringRef ObjTypeStr, - StringRef RelativeRootPath) override; - // Populates templates with CSS stylesheets, JS scripts paths. - Error setupTemplateResources(const ClangDocContext &CDCtx, json::Value &V, - SmallString<128> RelativeRootPath); - llvm::Error generateDocumentation( - StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos, - const ClangDocContext &CDCtx, std::string DirName) override; -}; - -Error MustacheHTMLGenerator::setupTemplateFiles(const ClangDocContext &CDCtx) { - // Template files need to use the native path when they're opened, - // but have to be used in POSIX style when used in HTML. - auto ConvertToNative = [](std::string &&Path) -> std::string { - SmallString<128> PathBuf(Path); - llvm::sys::path::native(PathBuf); - return PathBuf.str().str(); - }; - - std::string NamespaceFilePath = - ConvertToNative(CDCtx.MustacheTemplates.lookup("namespace-template")); - std::string ClassFilePath = - ConvertToNative(CDCtx.MustacheTemplates.lookup("class-template")); - std::string CommentFilePath = - ConvertToNative(CDCtx.MustacheTemplates.lookup("comment-template")); - std::string FunctionFilePath = - ConvertToNative(CDCtx.MustacheTemplates.lookup("function-template")); - std::string EnumFilePath = - ConvertToNative(CDCtx.MustacheTemplates.lookup("enum-template")); - std::vector<std::pair<StringRef, StringRef>> Partials = { - {"Comments", CommentFilePath}, - {"FunctionPartial", FunctionFilePath}, - {"EnumPartial", EnumFilePath}}; - - if (Error Err = setupTemplate(NamespaceTemplate, NamespaceFilePath, Partials)) - return Err; - - if (Error Err = setupTemplate(RecordTemplate, ClassFilePath, Partials)) - return Err; - - return Error::success(); -} - -Error MustacheHTMLGenerator::setupTemplateResources( - const ClangDocContext &CDCtx, json::Value &V, - SmallString<128> RelativeRootPath) { - V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName}); - json::Value StylesheetArr = Array(); - sys::path::native(RelativeRootPath, sys::path::Style::posix); - - auto *SSA = StylesheetArr.getAsArray(); - SSA->reserve(CDCtx.UserStylesheets.size()); - for (const auto &FilePath : CDCtx.UserStylesheets) { - SmallString<128> StylesheetPath = RelativeRootPath; - sys::path::append(StylesheetPath, sys::path::Style::posix, - sys::path::filename(FilePath)); - SSA->emplace_back(StylesheetPath); - } - V.getAsObject()->insert({"Stylesheets", StylesheetArr}); - - json::Value ScriptArr = Array(); - auto *SCA = ScriptArr.getAsArray(); - SCA->reserve(CDCtx.JsScripts.size()); - for (auto Script : CDCtx.JsScripts) { - SmallString<128> JsPath = RelativeRootPath; - sys::path::append(JsPath, sys::path::Style::posix, - sys::path::filename(Script)); - SCA->emplace_back(JsPath); - } - V.getAsObject()->insert({"Scripts", ScriptArr}); - return Error::success(); -} - -Error MustacheHTMLGenerator::generateDocForJSON(json::Value &JSON, - raw_fd_ostream &OS, - const ClangDocContext &CDCtx, - StringRef ObjTypeStr, - StringRef RelativeRootPath) { - if (ObjTypeStr == "namespace") { - if (auto Err = setupTemplateResources(CDCtx, JSON, RelativeRootPath)) - return Err; - assert(NamespaceTemplate && "NamespaceTemplate is nullptr."); - NamespaceTemplate->render(JSON, OS); - } else if (ObjTypeStr == "record") { - if (auto Err = setupTemplateResources(CDCtx, JSON, RelativeRootPath)) - return Err; - assert(RecordTemplate && "RecordTemplate is nullptr."); - RecordTemplate->render(JSON, OS); - } - return Error::success(); -} - -Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS, - const ClangDocContext &CDCtx) { - switch (I->IT) { - case InfoType::IT_enum: - case InfoType::IT_function: - case InfoType::IT_typedef: - case InfoType::IT_namespace: - case InfoType::IT_record: - case InfoType::IT_concept: - case InfoType::IT_variable: - case InfoType::IT_friend: - break; - case InfoType::IT_default: - return createStringError(inconvertibleErrorCode(), "unexpected InfoType"); - } - return Error::success(); -} - -Error MustacheHTMLGenerator::createResources(ClangDocContext &CDCtx) { - std::string ResourcePath(CDCtx.OutDirectory + "/html"); - for (const auto &FilePath : CDCtx.UserStylesheets) - if (Error Err = copyFile(FilePath, ResourcePath)) - return Err; - for (const auto &FilePath : CDCtx.JsScripts) - if (Error Err = copyFile(FilePath, ResourcePath)) - return Err; - return Error::success(); -} - -Error MustacheHTMLGenerator::generateDocumentation( - StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos, - const ClangDocContext &CDCtx, std::string DirName) { - return MustacheGenerator::generateDocumentation(RootDir, std::move(Infos), - CDCtx, "html"); -} - -const char *MustacheHTMLGenerator::Format = "mustache"; - -static GeneratorRegistry::Add<MustacheHTMLGenerator> - MHTML(MustacheHTMLGenerator::Format, "Generator for mustache HTML output."); - -// This anchor is used to force the linker to link in the generated object -// file and thus register the generator. -volatile int MHTMLGeneratorAnchorSource = 0; - -} // namespace doc -} // namespace clang diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp index 97c599a3f605..83fa55678279 100644 --- a/clang-tools-extra/clang-doc/JSONGenerator.cpp +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -84,8 +84,23 @@ serializeLocation(const Location &Loc, return LocationObj; } +/// Insert comments into a key in the Description object. +/// +/// \param Comment Either an Object or Array, depending on the comment type +/// \param Key The type (Brief, Code, etc.) of comment to be inserted static void insertComment(Object &Description, json::Value &Comment, StringRef Key) { + // The comment has a Children array for the actual text, with meta attributes + // alongside it in the Object. + if (auto *Obj = Comment.getAsObject()) { + if (auto *Children = Obj->getArray("Children"); Children->empty()) + return; + } + // The comment is just an array of text comments. + else if (auto *Array = Comment.getAsArray(); Array->empty()) { + return; + } + auto DescriptionIt = Description.find(Key); if (DescriptionIt == Description.end()) { @@ -98,10 +113,28 @@ static void insertComment(Object &Description, json::Value &Comment, } } +/// Takes the nested "Children" array from a comment Object. +/// +/// \return a json::Array of comments, possible json::Value::Kind::Null static json::Value extractTextComments(Object *ParagraphComment) { if (!ParagraphComment) - return json::Object(); - return *ParagraphComment->get("Children"); + return json::Value(nullptr); + json::Value *Children = ParagraphComment->get("Children"); + if (!Children) + return json::Value(nullptr); + auto ChildrenArray = *Children->getAsArray(); + auto ChildrenIt = ChildrenArray.begin(); + while (ChildrenIt != ChildrenArray.end()) { + auto *ChildObj = ChildrenIt->getAsObject(); + assert(ChildObj && "Invalid JSON object in Comment"); + auto TextComment = ChildObj->getString("TextComment"); + if (!TextComment || TextComment->empty()) { + ChildrenIt = ChildrenArray.erase(ChildrenIt); + continue; + } + ++ChildrenIt; + } + return ChildrenArray; } static json::Value extractVerbatimComments(json::Array VerbatimLines) { @@ -131,7 +164,8 @@ static Object serializeComment(const CommentInfo &I, Object &Description) { switch (I.Kind) { case CommentKind::CK_TextComment: { - Obj.insert({commentKindToString(I.Kind), I.Text}); + if (!I.Text.empty()) + Obj.insert({commentKindToString(I.Kind), I.Text}); return Obj; } @@ -265,6 +299,9 @@ serializeCommonAttributes(const Info &I, json::Object &Obj, if (auto *ParagraphComment = Comment.getAsObject(); ParagraphComment->get("ParagraphComment")) { auto TextCommentsArray = extractTextComments(ParagraphComment); + if (TextCommentsArray.kind() == json::Value::Null || + TextCommentsArray.getAsArray()->empty()) + continue; insertComment(Description, TextCommentsArray, "ParagraphComments"); } } @@ -508,23 +545,30 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj, json::Array &PubMembersArrayRef = *PublicMembersArray.getAsArray(); json::Value ProtectedMembersArray = Array(); json::Array &ProtMembersArrayRef = *ProtectedMembersArray.getAsArray(); + json::Value PrivateMembersArray = Array(); + json::Array &PrivateMembersArrayRef = *PrivateMembersArray.getAsArray(); for (const MemberTypeInfo &Member : I.Members) { json::Value MemberVal = Object(); auto &MemberObj = *MemberVal.getAsObject(); MemberObj["Name"] = Member.Name; MemberObj["Type"] = Member.Type.Name; + MemberObj["IsStatic"] = Member.IsStatic; if (Member.Access == AccessSpecifier::AS_public) PubMembersArrayRef.push_back(MemberVal); else if (Member.Access == AccessSpecifier::AS_protected) ProtMembersArrayRef.push_back(MemberVal); + else if (Member.Access == AccessSpecifier::AS_private) + PrivateMembersArrayRef.push_back(MemberVal); } if (!PubMembersArrayRef.empty()) insertArray(Obj, PublicMembersArray, "PublicMembers"); if (!ProtMembersArrayRef.empty()) Obj["ProtectedMembers"] = ProtectedMembersArray; + if (!PrivateMembersArrayRef.empty()) + insertArray(Obj, PrivateMembersArray, "PrivateMembers"); } if (!I.Bases.empty()) @@ -534,12 +578,16 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj, serializeInfo(Base, BaseObj, RepositoryUrl); }); - if (!I.Parents.empty()) + if (!I.Parents.empty()) { serializeArray(I.Parents, Obj, "Parents", SerializeReferenceLambda); + Obj["HasParents"] = true; + } - if (!I.VirtualParents.empty()) + if (!I.VirtualParents.empty()) { serializeArray(I.VirtualParents, Obj, "VirtualParents", SerializeReferenceLambda); + Obj["HasVirtualParents"] = true; + } if (I.Template) serializeInfo(I.Template.value(), Obj); @@ -562,6 +610,8 @@ static void serializeInfo(const VarInfo &I, json::Object &Obj, static void serializeInfo(const NamespaceInfo &I, json::Object &Obj, const std::optional<StringRef> RepositoryUrl) { serializeCommonAttributes(I, Obj, RepositoryUrl); + if (I.USR == GlobalNamespaceID) + Obj["Name"] = "Global Namespace"; if (!I.Children.Namespaces.empty()) serializeArray(I.Children.Namespaces, Obj, "Namespaces", diff --git a/clang-tools-extra/clang-doc/Mapper.cpp b/clang-tools-extra/clang-doc/Mapper.cpp index 497b80cf9746..24e798af52e6 100644 --- a/clang-tools-extra/clang-doc/Mapper.cpp +++ b/clang-tools-extra/clang-doc/Mapper.cpp @@ -95,10 +95,10 @@ bool MapASTVisitor::mapDecl(const T *D, bool IsDefinition) { // this decl for some reason (e.g. we're only reporting public decls). if (Child) CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(Child->USR)), - serialize::serialize(Child)); + serialize::serialize(Child, CDCtx.Diags)); if (Parent) CDCtx.ECtx->reportResult(llvm::toHex(llvm::toStringRef(Parent->USR)), - serialize::serialize(Parent)); + serialize::serialize(Parent, CDCtx.Diags)); } return true; } diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index 929112f01fb4..8eab5fb992eb 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -82,12 +82,10 @@ llvm::StringRef commentKindToString(CommentKind Kind) { llvm_unreachable("Unhandled CommentKind"); } -namespace { - const SymbolID EmptySID = SymbolID(); template <typename T> -llvm::Expected<std::unique_ptr<Info>> +static llvm::Expected<std::unique_ptr<Info>> reduce(std::vector<std::unique_ptr<Info>> &Values) { if (Values.empty() || !Values[0]) return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -102,7 +100,7 @@ reduce(std::vector<std::unique_ptr<Info>> &Values) { // Return the index of the matching child in the vector, or -1 if merge is not // necessary. template <typename T> -int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) { +static int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) { for (unsigned long I = 0; I < Children.size(); I++) { if (ChildToMerge.USR == Children[I].USR) return I; @@ -111,8 +109,8 @@ int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) { } template <typename T> -void reduceChildren(std::vector<T> &Children, - std::vector<T> &&ChildrenToMerge) { +static void reduceChildren(std::vector<T> &Children, + std::vector<T> &&ChildrenToMerge) { for (auto &ChildToMerge : ChildrenToMerge) { int MergeIdx = getChildIndexIfExists(Children, ChildToMerge); if (MergeIdx == -1) { @@ -123,8 +121,6 @@ void reduceChildren(std::vector<T> &Children, } } -} // namespace - // Dispatch function. llvm::Expected<std::unique_ptr<Info>> mergeInfos(std::vector<std::unique_ptr<Info>> &Values) { @@ -402,7 +398,7 @@ BaseRecordInfo::BaseRecordInfo() : RecordInfo() {} BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, bool IsVirtual, AccessSpecifier Access, bool IsParent) - : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access), + : RecordInfo(USR, Name, Path), Access(Access), IsVirtual(IsVirtual), IsParent(IsParent) {} llvm::SmallString<16> Info::extractName() const { @@ -481,10 +477,11 @@ ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx, StringRef RepositoryUrl, StringRef RepositoryLinePrefix, StringRef Base, std::vector<std::string> UserStylesheets, + clang::DiagnosticsEngine &Diags, bool FTimeTrace) - : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly), - FTimeTrace(FTimeTrace), OutDirectory(OutDirectory), - UserStylesheets(UserStylesheets), Base(Base) { + : ECtx(ECtx), ProjectName(ProjectName), OutDirectory(OutDirectory), + SourceRoot(std::string(SourceRoot)), UserStylesheets(UserStylesheets), + Base(Base), Diags(Diags), PublicOnly(PublicOnly), FTimeTrace(FTimeTrace) { llvm::SmallString<128> SourceRootDir(SourceRoot); if (SourceRoot.empty()) // If no SourceRoot was provided the current path is used as the default diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index 1ae067f73647..a3e779aa39bc 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_DOC_REPRESENTATION_H #include "clang/AST/Type.h" +#include "clang/Basic/Diagnostic.h" #include "clang/Basic/Specifiers.h" #include "clang/Tooling/Execution.h" #include "llvm/ADT/SmallVector.h" @@ -85,29 +86,29 @@ struct CommentInfo { // the vector. bool operator<(const CommentInfo &Other) const; - CommentKind Kind = CommentKind:: - CK_Unknown; // Kind of comment (FullComment, ParagraphComment, - // TextComment, InlineCommandComment, HTMLStartTagComment, - // HTMLEndTagComment, BlockCommandComment, - // ParamCommandComment, TParamCommandComment, - // VerbatimBlockComment, VerbatimBlockLineComment, - // VerbatimLineComment). - SmallString<64> Text; // Text of the comment. - SmallString<16> Name; // Name of the comment (for Verbatim and HTML). + std::vector<std::unique_ptr<CommentInfo>> + Children; // List of child comments for this CommentInfo. SmallString<8> Direction; // Parameter direction (for (T)ParamCommand). + SmallString<16> Name; // Name of the comment (for Verbatim and HTML). SmallString<16> ParamName; // Parameter name (for (T)ParamCommand). SmallString<16> CloseName; // Closing tag name (for VerbatimBlock). - bool SelfClosing = false; // Indicates if tag is self-closing (for HTML). - bool Explicit = false; // Indicates if the direction of a param is explicit - // (for (T)ParamCommand). + SmallString<64> Text; // Text of the comment. llvm::SmallVector<SmallString<16>, 4> AttrKeys; // List of attribute keys (for HTML). llvm::SmallVector<SmallString<16>, 4> AttrValues; // List of attribute values for each key (for HTML). llvm::SmallVector<SmallString<16>, 4> Args; // List of arguments to commands (for InlineCommand). - std::vector<std::unique_ptr<CommentInfo>> - Children; // List of child comments for this CommentInfo. + CommentKind Kind = CommentKind:: + CK_Unknown; // Kind of comment (FullComment, ParagraphComment, + // TextComment, InlineCommandComment, HTMLStartTagComment, + // HTMLEndTagComment, BlockCommandComment, + // ParamCommandComment, TParamCommandComment, + // VerbatimBlockComment, VerbatimBlockLineComment, + // VerbatimLineComment). + bool SelfClosing = false; // Indicates if tag is self-closing (for HTML). + bool Explicit = false; // Indicates if the direction of a param is explicit + // (for (T)ParamCommand). }; struct Reference { @@ -118,13 +119,13 @@ struct Reference { // "GlobalNamespace" as the name, but an empty QualName). Reference(SymbolID USR = SymbolID(), StringRef Name = StringRef(), InfoType IT = InfoType::IT_default) - : USR(USR), Name(Name), QualName(Name), RefType(IT) {} + : USR(USR), RefType(IT), Name(Name), QualName(Name) {} Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName, StringRef Path = StringRef()) - : USR(USR), Name(Name), QualName(QualName), RefType(IT), Path(Path) {} + : USR(USR), RefType(IT), Name(Name), QualName(QualName), Path(Path) {} Reference(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName, StringRef Path, SmallString<16> DocumentationFileName) - : USR(USR), Name(Name), QualName(QualName), RefType(IT), Path(Path), + : USR(USR), RefType(IT), Name(Name), QualName(QualName), Path(Path), DocumentationFileName(DocumentationFileName) {} bool operator==(const Reference &Other) const { @@ -144,6 +145,10 @@ struct Reference { SymbolID USR = SymbolID(); // Unique identifier for referenced decl + InfoType RefType = InfoType::IT_default; // Indicates the type of this + // Reference (namespace, record, + // function, enum, default). + // Name of type (possibly unresolved). Not including namespaces or template // parameters (so for a std::vector<int> this would be "vector"). See also // QualName. @@ -154,9 +159,6 @@ struct Reference { // Name. SmallString<16> QualName; - InfoType RefType = InfoType::IT_default; // Indicates the type of this - // Reference (namespace, record, - // function, enum, default). // Path of directory where the clang-doc generated file will be saved // (possibly unresolved) llvm::SmallString<128> Path; @@ -276,21 +278,21 @@ struct MemberTypeInfo : public FieldTypeInfo { Other.Description); } + std::vector<CommentInfo> Description; + // Access level associated with this info (public, protected, private, none). // AS_public is set as default because the bitcode writer requires the enum // with value 0 to be used as the default. // (AS_public = 0, AS_protected = 1, AS_private = 2, AS_none = 3) AccessSpecifier Access = AccessSpecifier::AS_public; - - std::vector<CommentInfo> Description; // Comment description of this field. bool IsStatic = false; }; struct Location { Location(int StartLineNumber = 0, int EndLineNumber = 0, StringRef Filename = StringRef(), bool IsFileInRootDir = false) - : StartLineNumber(StartLineNumber), EndLineNumber(EndLineNumber), - Filename(Filename), IsFileInRootDir(IsFileInRootDir) {} + : Filename(Filename), StartLineNumber(StartLineNumber), + EndLineNumber(EndLineNumber), IsFileInRootDir(IsFileInRootDir) {} bool operator==(const Location &Other) const { return std::tie(StartLineNumber, EndLineNumber, Filename) == @@ -308,40 +310,24 @@ struct Location { std::tie(Other.StartLineNumber, Other.EndLineNumber, Other.Filename); } - int StartLineNumber = 0; // Line number of this Location. + SmallString<32> Filename; + int StartLineNumber = 0; int EndLineNumber = 0; - SmallString<32> Filename; // File for this Location. - bool IsFileInRootDir = false; // Indicates if file is inside root directory + bool IsFileInRootDir = false; }; /// A base struct for Infos. struct Info { Info(InfoType IT = InfoType::IT_default, SymbolID USR = SymbolID(), StringRef Name = StringRef(), StringRef Path = StringRef()) - : USR(USR), IT(IT), Name(Name), Path(Path) {} + : Path(Path), Name(Name), USR(USR), IT(IT) {} Info(const Info &Other) = delete; Info(Info &&Other) = default; - virtual ~Info() = default; Info &operator=(Info &&Other) = default; - SymbolID USR = - SymbolID(); // Unique identifier for the decl described by this Info. - InfoType IT = InfoType::IT_default; // InfoType of this particular Info. - SmallString<16> Name; // Unqualified name of the decl. - llvm::SmallVector<Reference, 4> - Namespace; // List of parent namespaces for this decl. - std::vector<CommentInfo> Description; // Comment description of this decl. - llvm::SmallString<128> Path; // Path of directory where the clang-doc - // generated file will be saved - - // The name used for the file that this info is documented in. - // In the JSON generator, infos are documented in files with mangled names. - // Thus, we keep track of the physical filename for linking purposes. - SmallString<16> DocumentationFileName; - void mergeBase(Info &&I); bool mergeable(const Info &Other); @@ -352,6 +338,29 @@ struct Info { /// Returns the basename that should be used for this Info. llvm::SmallString<16> getFileBaseName() const; + + // Path of directory where the clang-doc generated file will be saved. + llvm::SmallString<128> Path; + + // Unqualified name of the decl. + SmallString<16> Name; + + // The name used for the file that this info is documented in. + // In the JSON generator, infos are documented in files with mangled names. + // Thus, we keep track of the physical filename for linking purposes. + SmallString<16> DocumentationFileName; + + // List of parent namespaces for this decl. + llvm::SmallVector<Reference, 4> Namespace; + + // Unique identifier for the decl described by this Info. + SymbolID USR = SymbolID(); + + // InfoType of this particular Info. + InfoType IT = InfoType::IT_default; + + // Comment description of this decl. + std::vector<CommentInfo> Description; }; // Info for namespaces. @@ -425,21 +434,21 @@ struct FunctionInfo : public SymbolInfo { void merge(FunctionInfo &&I); - bool IsMethod = false; // Indicates whether this function is a class method. - Reference Parent; // Reference to the parent class decl for this method. - TypeInfo ReturnType; // Info about the return type of this function. - llvm::SmallVector<FieldTypeInfo, 4> Params; // List of parameters. + Reference Parent; + TypeInfo ReturnType; + llvm::SmallVector<FieldTypeInfo, 4> Params; + SmallString<256> Prototype; + + // When present, this function is a template or specialization. + std::optional<TemplateInfo> Template; + // Access level for this method (public, private, protected, none). // AS_public is set as default because the bitcode writer requires the enum // with value 0 to be used as the default. // (AS_public = 0, AS_protected = 1, AS_private = 2, AS_none = 3) AccessSpecifier Access = AccessSpecifier::AS_public; - // Function Prototype - SmallString<256> Prototype; - - // When present, this function is a template or specialization. - std::optional<TemplateInfo> Template; + bool IsMethod = false; }; // TODO: Expand to allow for documenting templating, inheritance access, @@ -454,15 +463,15 @@ struct RecordInfo : public SymbolInfo { // Type of this record (struct, class, union, interface). TagTypeKind TagType = TagTypeKind::Struct; - // When present, this record is a template or specialization. - std::optional<TemplateInfo> Template; - // Indicates if the record was declared using a typedef. Things like anonymous // structs in a typedef: // typedef struct { ... } foo_t; // are converted into records with the typedef as the Name + this flag set. bool IsTypeDef = false; + // When present, this record is a template or specialization. + std::optional<TemplateInfo> Template; + llvm::SmallVector<MemberTypeInfo, 4> Members; // List of info about record members. llvm::SmallVector<Reference, 4> Parents; // List of base/parent records @@ -507,11 +516,11 @@ struct BaseRecordInfo : public RecordInfo { BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, bool IsVirtual, AccessSpecifier Access, bool IsParent); - // Indicates if base corresponds to a virtual inheritance - bool IsVirtual = false; // Access level associated with this inherited info (public, protected, // private). AccessSpecifier Access = AccessSpecifier::AS_public; + // Indicates if base corresponds to a virtual inheritance + bool IsVirtual = false; bool IsParent = false; // Indicates if this base is a direct parent }; @@ -598,17 +607,13 @@ llvm::Expected<std::unique_ptr<Info>> mergeInfos(std::vector<std::unique_ptr<Info>> &Values); struct ClangDocContext { - ClangDocContext() = default; ClangDocContext(tooling::ExecutionContext *ECtx, StringRef ProjectName, bool PublicOnly, StringRef OutDirectory, StringRef SourceRoot, StringRef RepositoryUrl, StringRef RepositoryCodeLinePrefix, StringRef Base, std::vector<std::string> UserStylesheets, - bool FTimeTrace = false); + clang::DiagnosticsEngine &Diags, bool FTimeTrace = false); tooling::ExecutionContext *ECtx; - std::string ProjectName; // Name of project clang-doc is documenting. - bool PublicOnly; // Indicates if only public declarations are documented. - bool FTimeTrace; // Indicates if ftime trace is turned on - int Granularity; // Granularity of ftime trace + std::string ProjectName; // Name of project clang-doc is documenting. std::string OutDirectory; // Directory for outputting generated files. std::string SourceRoot; // Directory where processed files are stored. Links // to definition locations will only be generated if @@ -627,7 +632,12 @@ struct ClangDocContext { // Maps mustache template types to specific mustache template files. // Ex. comment-template -> /path/to/comment-template.mustache llvm::StringMap<std::string> MustacheTemplates; + // A pointer to a DiagnosticsEngine for error reporting. + clang::DiagnosticsEngine &Diags; Index Idx; + int Granularity; // Granularity of ftime trace + bool PublicOnly; // Indicates if only public declarations are documented. + bool FTimeTrace; // Indicates if ftime trace is turned on }; } // namespace doc diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp index e1d60570da4c..29f8faf94713 100644 --- a/clang-tools-extra/clang-doc/Serialize.cpp +++ b/clang-tools-extra/clang-doc/Serialize.cpp @@ -337,28 +337,29 @@ static std::string getSourceCode(const Decl *D, const SourceRange &R) { .str(); } -template <typename T> static std::string serialize(T &I) { +template <typename T> +static std::string serialize(T &I, DiagnosticsEngine &Diags) { SmallString<2048> Buffer; llvm::BitstreamWriter Stream(Buffer); - ClangDocBitcodeWriter Writer(Stream); + ClangDocBitcodeWriter Writer(Stream, Diags); Writer.emitBlock(I); return Buffer.str().str(); } -std::string serialize(std::unique_ptr<Info> &I) { +std::string serialize(std::unique_ptr<Info> &I, DiagnosticsEngine &Diags) { switch (I->IT) { case InfoType::IT_namespace: - return serialize(*static_cast<NamespaceInfo *>(I.get())); + return serialize(*static_cast<NamespaceInfo *>(I.get()), Diags); case InfoType::IT_record: - return serialize(*static_cast<RecordInfo *>(I.get())); + return serialize(*static_cast<RecordInfo *>(I.get()), Diags); case InfoType::IT_enum: - return serialize(*static_cast<EnumInfo *>(I.get())); + return serialize(*static_cast<EnumInfo *>(I.get()), Diags); case InfoType::IT_function: - return serialize(*static_cast<FunctionInfo *>(I.get())); + return serialize(*static_cast<FunctionInfo *>(I.get()), Diags); case InfoType::IT_concept: - return serialize(*static_cast<ConceptInfo *>(I.get())); + return serialize(*static_cast<ConceptInfo *>(I.get()), Diags); case InfoType::IT_variable: - return serialize(*static_cast<VarInfo *>(I.get())); + return serialize(*static_cast<VarInfo *>(I.get()), Diags); case InfoType::IT_friend: case InfoType::IT_typedef: case InfoType::IT_default: diff --git a/clang-tools-extra/clang-doc/Serialize.h b/clang-tools-extra/clang-doc/Serialize.h index 30ed836d57de..cfba5c987c23 100644 --- a/clang-tools-extra/clang-doc/Serialize.h +++ b/clang-tools-extra/clang-doc/Serialize.h @@ -80,7 +80,7 @@ emitInfo(const VarDecl *D, const FullComment *FC, const Location &Loc, // memory (vs storing USRs directly). SymbolID hashUSR(llvm::StringRef USR); -std::string serialize(std::unique_ptr<Info> &I); +std::string serialize(std::unique_ptr<Info> &I, DiagnosticsEngine &Diags); } // namespace serialize } // namespace doc diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustache index bbac3ffae02f..52caebb503e2 100644 --- a/clang-tools-extra/clang-doc/assets/class-template.mustache +++ b/clang-tools-extra/clang-doc/assets/class-template.mustache @@ -7,232 +7,203 @@ }} <!DOCTYPE html> <html lang="en-US"> -<head> - <meta charset="utf-8"/> - <title>{{Name}} - {{#Stylesheets}} - - {{/Stylesheets}} - {{#Scripts}} - - {{/Scripts}} - {{! Highlight.js dependency for syntax highlighting }} - - - - +{{>HeadPartial}} - -
-
- + {{/ProtectedMembers}} {{#HasPublicFunctions}} - -
  • -
      +
      +

      Public Methods

      +
      {{#PublicFunctions}} - + {{>FunctionPartial}} {{/PublicFunctions}} -
    -
  • - {{/HasPublicFunctions}} +
    + + {{/PublicFunctions}} {{#ProtectedFunction}} -
  • -
      - {{#Obj}} - - {{/Obj}} -
    -
  • +
      + {{#Obj}} + + {{/Obj}} +
    {{/ProtectedFunction}} {{#Enums}} - -
  • -
      +
      +

      Enumerations

      +
      {{#Obj}} - + {{>EnumPartial}} {{/Obj}} -
    -
  • + + {{/Enums}} - {{#Typedef}} - - {{/Typedef}} {{#Record}} - -
  • - - -
    -
    -
    -
    -

    {{TagType}} {{Name}}

    -

    Defined at line {{Location.LineNumber}} of file {{Location.Filename}}

    - {{#Description}} -
    - {{>Comments}} -
    - {{/Description}} -
    -
    - {{#HasPublicMembers}} -
    -

    Public Members

    -
    - {{#PublicMembers}} -
    -
    {{Type}} {{Name}}
    - {{#MemberComments}} -
    - {{>Comments}} -
    - {{/MemberComments}} -
    - {{/PublicMembers}} -
    -
    - {{/HasPublicMembers}} - {{#ProtectedMembers}} -
    -

    Protected Members

    -
    - {{#Obj}} -
    -
    {{Type}} {{Name}}
    - {{#MemberComments}} -
    - {{>Comments}} -
    - {{/MemberComments}} -
    - {{/Obj}} -
    -
    - {{/ProtectedMembers}} - {{#HasPublicFunctions}} -
    -

    Public Methods

    -
    - {{#PublicFunctions}} - {{>FunctionPartial}} - {{/PublicFunctions}} -
    -
    - {{/PublicFunctions}} - {{#ProtectedFunction}} -
  • -
      - {{#Obj}} - - {{/Obj}} -
    - {{/ProtectedFunction}} - {{#Enums}} -
    -

    Enumerations

    -
    - {{#Obj}} -{{>EnumPartial}} - {{/Obj}} -
    -
    - {{/Enums}} - {{#Record}} -
    -

    Inner Classes

    - -
    - {{/Record}} - {{#Typedef}} -
    -

    Enums

    -
    - {{/Typedef}} + {{#Typedef}} +
    +

    Enums

    +
    + {{/Typedef}} + - -
    + diff --git a/clang-tools-extra/clang-doc/assets/enum-template.mustache b/clang-tools-extra/clang-doc/assets/enum-template.mustache index 53da4669d824..ec42df99a7f4 100644 --- a/clang-tools-extra/clang-doc/assets/enum-template.mustache +++ b/clang-tools-extra/clang-doc/assets/enum-template.mustache @@ -7,22 +7,18 @@ }}
    -
    -            
    -enum {{Name}}
    -            
    -        
    +
    enum {{Name}}
    {{! Enum Values }} - - - - {{#HasComment}} + + + + {{#HasComment}} - {{/HasComment}} - + {{/HasComment}} + {{#Members}} @@ -34,7 +30,7 @@ enum {{Name}} {{/Value}} {{#EnumValueComments}} - + {{/EnumValueComments}} {{/Members}} diff --git a/clang-tools-extra/clang-doc/assets/head-template.mustache b/clang-tools-extra/clang-doc/assets/head-template.mustache new file mode 100644 index 000000000000..f0a8057a9c3d --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/head-template.mustache @@ -0,0 +1,14 @@ + + + {{Name}} + {{#Stylesheets}} + + {{/Stylesheets}} + {{#Scripts}} + + {{/Scripts}} + {{! Highlight.js dependency for syntax highlighting }} + + + + diff --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache b/clang-tools-extra/clang-doc/assets/namespace-template.mustache index 8a5be46aee28..7e00821c4914 100644 --- a/clang-tools-extra/clang-doc/assets/namespace-template.mustache +++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache @@ -7,104 +7,72 @@ }} - - - {{NamespaceTitle}} - {{#Stylesheets}} - - {{/Stylesheets}} - {{#Scripts}} - - {{/Scripts}} - {{! Highlight.js dependency for syntax highlighting }} - - - - - - -
    -
    - -
    -
    +{{>HeadPartial}} + + {{>NavbarPartial}} +
    +
    + + +
    +
    +
    + {{#HasEnums}} +
    +

    Enumerations

    +
    + {{#Enums}} + {{>EnumPartial}} + {{/Enums}} +
    +
    + {{/HasEnums}} + {{#HasRecords}} +
    +

    Inner Classes

    + +
    + {{/HasRecords}}
    -
    - +
    +
    + diff --git a/clang-tools-extra/clang-doc/assets/navbar-template.mustache b/clang-tools-extra/clang-doc/assets/navbar-template.mustache new file mode 100644 index 000000000000..178d147a556d --- /dev/null +++ b/clang-tools-extra/clang-doc/assets/navbar-template.mustache @@ -0,0 +1,19 @@ + diff --git a/clang-tools-extra/clang-doc/support/Utils.cpp b/clang-tools-extra/clang-doc/support/Utils.cpp index 6ed56033738b..d0fd6f45b8a0 100644 --- a/clang-tools-extra/clang-doc/support/Utils.cpp +++ b/clang-tools-extra/clang-doc/support/Utils.cpp @@ -28,13 +28,24 @@ SmallString<128> appendPathPosix(StringRef Base, StringRef Path) { return Default; } -void getMustacheHtmlFiles(StringRef AssetsPath, - clang::doc::ClangDocContext &CDCtx) { +void getHtmlFiles(StringRef AssetsPath, clang::doc::ClangDocContext &CDCtx) { assert(!AssetsPath.empty()); assert(sys::fs::is_directory(AssetsPath)); - SmallString<128> DefaultStylesheet = - appendPathPosix(AssetsPath, "clang-doc-mustache.css"); + // TODO: Allow users to override default templates with their own. We would + // similarly have to check if a template file already exists in CDCtx. + if (CDCtx.UserStylesheets.empty()) { + SmallString<128> DefaultStylesheet = + appendPathPosix(AssetsPath, "clang-doc-mustache.css"); + CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(), + DefaultStylesheet.c_str()); + } + + if (CDCtx.JsScripts.empty()) { + SmallString<128> IndexJS = appendPathPosix(AssetsPath, "mustache-index.js"); + CDCtx.JsScripts.insert(CDCtx.JsScripts.begin(), IndexJS.c_str()); + } + SmallString<128> NamespaceTemplate = appendPathPosix(AssetsPath, "namespace-template.mustache"); SmallString<128> ClassTemplate = @@ -45,11 +56,11 @@ void getMustacheHtmlFiles(StringRef AssetsPath, appendPathPosix(AssetsPath, "function-template.mustache"); SmallString<128> CommentTemplate = appendPathPosix(AssetsPath, "comment-template.mustache"); - SmallString<128> IndexJS = appendPathPosix(AssetsPath, "mustache-index.js"); + SmallString<128> HeadTemplate = + appendPathPosix(AssetsPath, "head-template.mustache"); + SmallString<128> NavbarTemplate = + appendPathPosix(AssetsPath, "navbar-template.mustache"); - CDCtx.JsScripts.insert(CDCtx.JsScripts.begin(), IndexJS.c_str()); - CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(), - DefaultStylesheet.c_str()); CDCtx.MustacheTemplates.insert( {"namespace-template", NamespaceTemplate.c_str()}); CDCtx.MustacheTemplates.insert({"class-template", ClassTemplate.c_str()}); @@ -57,4 +68,6 @@ void getMustacheHtmlFiles(StringRef AssetsPath, CDCtx.MustacheTemplates.insert( {"function-template", FunctionTemplate.c_str()}); CDCtx.MustacheTemplates.insert({"comment-template", CommentTemplate.c_str()}); + CDCtx.MustacheTemplates.insert({"head-template", HeadTemplate.c_str()}); + CDCtx.MustacheTemplates.insert({"navbar-template", NavbarTemplate.c_str()}); } diff --git a/clang-tools-extra/clang-doc/support/Utils.h b/clang-tools-extra/clang-doc/support/Utils.h index 8161c37503f8..f4ed9ec42dce 100644 --- a/clang-tools-extra/clang-doc/support/Utils.h +++ b/clang-tools-extra/clang-doc/support/Utils.h @@ -20,7 +20,7 @@ llvm::SmallString<128> appendPathNative(llvm::StringRef Base, llvm::SmallString<128> appendPathPosix(llvm::StringRef Base, llvm::StringRef Path); -void getMustacheHtmlFiles(llvm::StringRef AssetsPath, - clang::doc::ClangDocContext &CDCtx); +void getHtmlFiles(llvm::StringRef AssetsPath, + clang::doc::ClangDocContext &CDCtx); #endif diff --git a/clang-tools-extra/clang-doc/tool/CMakeLists.txt b/clang-tools-extra/clang-doc/tool/CMakeLists.txt index 7c9a94adec8a..c8b4162a0e71 100644 --- a/clang-tools-extra/clang-doc/tool/CMakeLists.txt +++ b/clang-tools-extra/clang-doc/tool/CMakeLists.txt @@ -31,6 +31,8 @@ set(assets function-template.mustache namespace-template.mustache template.mustache + head-template.mustache + navbar-template.mustache ) set(asset_dir "${CMAKE_CURRENT_SOURCE_DIR}/../assets") diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index 62fa6a17df2e..ee4c44971887 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -22,6 +22,9 @@ #include "Generators.h" #include "Representation.h" #include "support/Utils.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Tooling/AllTUsExecution.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/Execution.h" @@ -108,21 +111,20 @@ Turn on time profiler. Generates clang-doc-tracing.json)"), llvm::cl::init(false), llvm::cl::cat(ClangDocCategory)); -enum OutputFormatTy { md, yaml, html, mustache, json }; - -static llvm::cl::opt FormatEnum( - "format", llvm::cl::desc("Format for outputted docs."), - llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml", - "Documentation in YAML format."), - clEnumValN(OutputFormatTy::md, "md", - "Documentation in MD format."), - clEnumValN(OutputFormatTy::html, "html", - "Documentation in HTML format."), - clEnumValN(OutputFormatTy::mustache, "mustache", - "Documentation in mustache HTML format"), - clEnumValN(OutputFormatTy::json, "json", - "Documentation in JSON format")), - llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory)); +enum OutputFormatTy { md, yaml, html, json }; + +static llvm::cl::opt + FormatEnum("format", llvm::cl::desc("Format for outputted docs."), + llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml", + "Documentation in YAML format."), + clEnumValN(OutputFormatTy::md, "md", + "Documentation in MD format."), + clEnumValN(OutputFormatTy::html, "html", + "Documentation in HTML format."), + clEnumValN(OutputFormatTy::json, "json", + "Documentation in JSON format")), + llvm::cl::init(OutputFormatTy::yaml), + llvm::cl::cat(ClangDocCategory)); static llvm::ExitOnError ExitOnErr; @@ -134,8 +136,6 @@ static std::string getFormatString() { return "md"; case OutputFormatTy::html: return "html"; - case OutputFormatTy::mustache: - return "mustache"; case OutputFormatTy::json: return "json"; } @@ -151,6 +151,7 @@ static std::string getExecutablePath(const char *Argv0, void *MainAddr) { return llvm::sys::fs::getMainExecutable(Argv0, MainAddr); } +// TODO: Rename this, since it only gets custom CSS/JS static llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) { using DirIt = llvm::sys::fs::directory_iterator; std::error_code FileErr; @@ -171,58 +172,15 @@ static llvm::Error getAssetFiles(clang::doc::ClangDocContext &CDCtx) { return llvm::Error::success(); } -static llvm::Error getDefaultAssetFiles(const char *Argv0, - clang::doc::ClangDocContext &CDCtx) { - void *MainAddr = (void *)(intptr_t)getExecutablePath; - std::string ClangDocPath = getExecutablePath(Argv0, MainAddr); - llvm::SmallString<128> NativeClangDocPath; - llvm::sys::path::native(ClangDocPath, NativeClangDocPath); - - llvm::SmallString<128> AssetsPath; - AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); - llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); - llvm::SmallString<128> DefaultStylesheet = - appendPathNative(AssetsPath, "clang-doc-default-stylesheet.css"); - llvm::SmallString<128> IndexJS = appendPathNative(AssetsPath, "index.js"); - - if (!llvm::sys::fs::is_regular_file(IndexJS)) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "default index.js file missing at " + - IndexJS + "\n"); - - if (!llvm::sys::fs::is_regular_file(DefaultStylesheet)) - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "default clang-doc-default-stylesheet.css file missing at " + - DefaultStylesheet + "\n"); - - CDCtx.UserStylesheets.insert(CDCtx.UserStylesheets.begin(), - std::string(DefaultStylesheet)); - CDCtx.JsScripts.emplace_back(IndexJS.str()); - - return llvm::Error::success(); -} - -static llvm::Error getHtmlAssetFiles(const char *Argv0, - clang::doc::ClangDocContext &CDCtx) { - if (!UserAssetPath.empty() && - !llvm::sys::fs::is_directory(std::string(UserAssetPath))) - llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath - << " falling back to default\n"; - if (llvm::sys::fs::is_directory(std::string(UserAssetPath))) - return getAssetFiles(CDCtx); - return getDefaultAssetFiles(Argv0, CDCtx); -} - -static llvm::Error getMustacheHtmlFiles(const char *Argv0, - clang::doc::ClangDocContext &CDCtx) { +static llvm::Error getHtmlFiles(const char *Argv0, + clang::doc::ClangDocContext &CDCtx) { bool IsDir = llvm::sys::fs::is_directory(UserAssetPath); if (!UserAssetPath.empty() && !IsDir) llvm::outs() << "Asset path supply is not a directory: " << UserAssetPath << " falling back to default\n"; if (IsDir) { - getMustacheHtmlFiles(UserAssetPath, CDCtx); - return llvm::Error::success(); + if (auto Err = getAssetFiles(CDCtx)) + return Err; } void *MainAddr = (void *)(intptr_t)getExecutablePath; std::string ClangDocPath = getExecutablePath(Argv0, MainAddr); @@ -233,7 +191,7 @@ static llvm::Error getMustacheHtmlFiles(const char *Argv0, AssetsPath = llvm::sys::path::parent_path(NativeClangDocPath); llvm::sys::path::append(AssetsPath, "..", "share", "clang-doc"); - getMustacheHtmlFiles(AssetsPath, CDCtx); + getHtmlFiles(AssetsPath, CDCtx); return llvm::Error::success(); } @@ -255,13 +213,16 @@ sortUsrToInfo(llvm::StringMap> &USRToInfo) { } } -static llvm::Error handleMappingFailures(llvm::Error Err) { +static llvm::Error handleMappingFailures(DiagnosticsEngine &Diags, + llvm::Error Err) { if (!Err) return llvm::Error::success(); if (IgnoreMappingFailures) { - llvm::errs() << "Error mapping decls in files. Clang-doc will ignore these " - "files and continue:\n" - << toString(std::move(Err)) << "\n"; + unsigned ID = Diags.getCustomDiagID( + DiagnosticsEngine::Warning, + "Error mapping decls in files. Clang-doc will ignore these files and " + "continue:\n%0"); + Diags.Report(ID) << toString(std::move(Err)); return llvm::Error::success(); } return Err; @@ -313,28 +274,25 @@ Example usage for a project using a compile commands database: tooling::ArgumentInsertPosition::END), ArgAdjuster); - clang::doc::ClangDocContext CDCtx = { - Executor->getExecutionContext(), - ProjectName, - PublicOnly, - OutDirectory, - SourceRoot, - RepositoryUrl, - RepositoryCodeLinePrefix, - BaseDirectory, - {UserStylesheets.begin(), UserStylesheets.end()}, - FTimeTrace}; - - if (Format == "html") { - ExitOnErr(getHtmlAssetFiles(argv[0], CDCtx)); - } else if (Format == "mustache") { - ExitOnErr(getMustacheHtmlFiles(argv[0], CDCtx)); - } + auto DiagOpts = std::make_unique(); + TextDiagnosticPrinter *DiagClient = + new TextDiagnosticPrinter(llvm::errs(), *DiagOpts); + IntrusiveRefCntPtr DiagID(new DiagnosticIDs()); + DiagnosticsEngine Diags(DiagID, *DiagOpts, DiagClient); + + clang::doc::ClangDocContext CDCtx( + Executor->getExecutionContext(), ProjectName, PublicOnly, OutDirectory, + SourceRoot, RepositoryUrl, RepositoryCodeLinePrefix, BaseDirectory, + {UserStylesheets.begin(), UserStylesheets.end()}, Diags, FTimeTrace); + + if (Format == "html") + ExitOnErr(getHtmlFiles(argv[0], CDCtx)); llvm::timeTraceProfilerBegin("Executor Launch", "total runtime"); // Mapping phase llvm::outs() << "Mapping decls...\n"; ExitOnErr(handleMappingFailures( + Diags, Executor->execute(doc::newMapperActionFactory(CDCtx), ArgAdjuster))); llvm::timeTraceProfilerEnd(); @@ -360,13 +318,18 @@ Example usage for a project using a compile commands database: std::atomic Error; Error = false; llvm::sys::Mutex IndexMutex; + llvm::sys::Mutex DiagMutex; + unsigned DiagIDBitcodeReading = Diags.getCustomDiagID( + DiagnosticsEngine::Error, "error reading bitcode: %0"); + unsigned DiagIDBitcodeMerging = Diags.getCustomDiagID( + DiagnosticsEngine::Error, "error merging bitcode: %0"); // ExecutorConcurrency is a flag exposed by AllTUsExecution.h llvm::DefaultThreadPool Pool( llvm::hardware_concurrency(ExecutorConcurrency)); { llvm::TimeTraceScope TS("Reduce"); for (auto &Group : USRToBitcode) { - Pool.async([&]() { // time trace decoding bitcode + Pool.async([&, &Diags = Diags]() { // time trace decoding bitcode if (FTimeTrace) llvm::timeTraceProfilerInitialize(200, "clang-doc"); @@ -375,10 +338,13 @@ Example usage for a project using a compile commands database: llvm::TimeTraceScope Red("decoding bitcode"); for (auto &Bitcode : Group.getValue()) { llvm::BitstreamCursor Stream(Bitcode); - doc::ClangDocBitcodeReader Reader(Stream); + doc::ClangDocBitcodeReader Reader(Stream, Diags); auto ReadInfos = Reader.readBitcode(); if (!ReadInfos) { - llvm::errs() << toString(ReadInfos.takeError()) << "\n"; + std::lock_guard Guard(DiagMutex); + + Diags.Report(DiagIDBitcodeReading) + << toString(ReadInfos.takeError()); Error = true; return; } @@ -394,7 +360,9 @@ Example usage for a project using a compile commands database: auto ExpReduced = doc::mergeInfos(Infos); if (!ExpReduced) { - llvm::errs() << llvm::toString(ExpReduced.takeError()); + std::lock_guard Guard(DiagMutex); + Diags.Report(DiagIDBitcodeMerging) + << toString(ExpReduced.takeError()); return; } Reduced = std::move(*ExpReduced); diff --git a/clang-tools-extra/clang-tidy/.clang-tidy b/clang-tools-extra/clang-tidy/.clang-tidy index 82d0df869717..70d57315260c 100644 --- a/clang-tools-extra/clang-tidy/.clang-tidy +++ b/clang-tools-extra/clang-tidy/.clang-tidy @@ -9,6 +9,11 @@ Checks: > -bugprone-narrowing-conversions, -bugprone-unchecked-optional-access, -bugprone-unused-return-value, + cppcoreguidelines-init-variables, + cppcoreguidelines-missing-std-forward, + cppcoreguidelines-rvalue-reference-param-not-moved, + cppcoreguidelines-virtual-class-destructor, + google-readability-casting, misc-const-correctness, modernize-*, -modernize-avoid-c-arrays, @@ -32,8 +37,7 @@ Checks: > -readability-qualified-auto, -readability-simplify-boolean-expr, -readability-static-definition-in-anonymous-namespace, - -readability-suspicious-call-argument, - -readability-use-anyofallof + -readability-suspicious-call-argument CheckOptions: - key: performance-move-const-arg.CheckTriviallyCopyableMove diff --git a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp index 6716d90a1aca..16a4d13b9aad 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp @@ -478,11 +478,10 @@ bool ClangTidyDiagnosticConsumer::passesLineFilter(StringRef FileName, if (FileName.ends_with(Filter.Name)) { if (Filter.LineRanges.empty()) return true; - for (const FileFilter::LineRange &Range : Filter.LineRanges) { - if (Range.first <= LineNumber && LineNumber <= Range.second) - return true; - } - return false; + return llvm::any_of( + Filter.LineRanges, [&](const FileFilter::LineRange &Range) { + return Range.first <= LineNumber && LineNumber <= Range.second; + }); } } return false; @@ -527,7 +526,8 @@ void ClangTidyDiagnosticConsumer::forwardDiagnostic(const Diagnostic &Info) { Builder << Qualifiers::fromOpaqueValue(Info.getRawArg(Index)); break; case clang::DiagnosticsEngine::ak_qualtype: - Builder << QualType::getFromOpaquePtr((void *)Info.getRawArg(Index)); + Builder << QualType::getFromOpaquePtr( + reinterpret_cast(Info.getRawArg(Index))); break; case clang::DiagnosticsEngine::ak_declarationname: Builder << DeclarationName::getFromOpaqueInteger(Info.getRawArg(Index)); diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp index 550f7809d75f..f61540069d07 100644 --- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp @@ -178,12 +178,13 @@ template <> struct MappingTraits { } }; -struct ChecksVariant { +struct GlobListVariant { std::optional AsString; std::optional> AsVector; }; -template <> void yamlize(IO &IO, ChecksVariant &Val, bool, EmptyContext &Ctx) { +template <> +void yamlize(IO &IO, GlobListVariant &Val, bool, EmptyContext &Ctx) { if (!IO.outputting()) { // Special case for reading from YAML // Must support reading from both a string or a list @@ -200,25 +201,26 @@ template <> void yamlize(IO &IO, ChecksVariant &Val, bool, EmptyContext &Ctx) { } } -static void mapChecks(IO &IO, std::optional &Checks) { +static void mapGlobList(IO &IO, std::optional &GlobList, + StringRef Key) { if (IO.outputting()) { // Output always a string - IO.mapOptional("Checks", Checks); + IO.mapOptional(Key, GlobList); } else { // Input as either a string or a list - ChecksVariant ChecksAsVariant; - IO.mapOptional("Checks", ChecksAsVariant); - if (ChecksAsVariant.AsString) - Checks = ChecksAsVariant.AsString; - else if (ChecksAsVariant.AsVector) - Checks = llvm::join(*ChecksAsVariant.AsVector, ","); + GlobListVariant GlobListAsVariant; + IO.mapOptional(Key, GlobListAsVariant); + if (GlobListAsVariant.AsString) + GlobList = GlobListAsVariant.AsString; + else if (GlobListAsVariant.AsVector) + GlobList = llvm::join(*GlobListAsVariant.AsVector, ","); } } template <> struct MappingTraits { static void mapping(IO &IO, ClangTidyOptions &Options) { - mapChecks(IO, Options.Checks); - IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors); + mapGlobList(IO, Options.Checks, "Checks"); + mapGlobList(IO, Options.WarningsAsErrors, "WarningsAsErrors"); IO.mapOptional("HeaderFileExtensions", Options.HeaderFileExtensions); IO.mapOptional("ImplementationFileExtensions", Options.ImplementationFileExtensions); diff --git a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h index 95216368492c..d72d021f4483 100644 --- a/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h +++ b/clang-tools-extra/clang-tidy/ExpandModularHeadersPPCallbacks.h @@ -137,7 +137,7 @@ class ExpandModularHeadersPPCallbacks : public PPCallbacks { std::unique_ptr PP; bool EnteredMainFile = false; bool StartedLexing = false; - Token CurrentToken; + Token CurrentToken = Token(); }; } // namespace tooling diff --git a/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp b/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp index a74ab008fa7c..b552ae8b1783 100644 --- a/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp +++ b/clang-tools-extra/clang-tidy/NoLintDirectiveHandler.cpp @@ -19,7 +19,6 @@ #include "clang/Basic/SourceManager.h" #include "clang/Tooling/Core/Diagnostic.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSwitch.h" diff --git a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp index 2b1312c8967d..d90305d14c48 100644 --- a/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp +++ b/clang-tools-extra/clang-tidy/altera/StructPackAlignCheck.cpp @@ -63,9 +63,8 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { const QualType StructFieldTy = StructField->getType(); if (StructFieldTy->isIncompleteType()) return; - const unsigned int StructFieldWidth = - (unsigned int)Result.Context->getTypeInfo(StructFieldTy.getTypePtr()) - .Width; + const unsigned int StructFieldWidth = static_cast( + Result.Context->getTypeInfo(StructFieldTy.getTypePtr()).Width); FieldSizes.emplace_back(StructFieldWidth, StructField->getFieldIndex()); // FIXME: Recommend a reorganization of the struct (sort by StructField // size, largest to smallest). @@ -79,7 +78,7 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { CharUnits::fromQuantity(std::max( std::ceil(static_cast(TotalBitSize) / CharSize), 1)); const CharUnits MaxAlign = CharUnits::fromQuantity( - std::ceil((float)Struct->getMaxAlignment() / CharSize)); + std::ceil(static_cast(Struct->getMaxAlignment()) / CharSize)); const CharUnits CurrAlign = Result.Context->getASTRecordLayout(Struct).getAlignment(); const CharUnits NewAlign = computeRecommendedAlignment(MinByteSize); @@ -99,8 +98,7 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { diag(Struct->getLocation(), "accessing fields in struct %0 is inefficient due to padding; only " "needs %1 bytes but is using %2 bytes") - << Struct << (int)MinByteSize.getQuantity() - << (int)CurrSize.getQuantity() + << Struct << MinByteSize.getQuantity() << CurrSize.getQuantity() << FixItHint::CreateInsertion(Struct->getEndLoc().getLocWithOffset(1), " __attribute__((packed))"); diag(Struct->getLocation(), @@ -112,8 +110,7 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { FixItHint FixIt; auto *Attribute = Struct->getAttr(); - const std::string NewAlignQuantity = - std::to_string((int)NewAlign.getQuantity()); + const std::string NewAlignQuantity = std::to_string(NewAlign.getQuantity()); if (Attribute) { FixIt = FixItHint::CreateReplacement( Attribute->getRange(), @@ -130,7 +127,7 @@ void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { diag(Struct->getLocation(), "accessing fields in struct %0 is inefficient due to poor alignment; " "currently aligned to %1 bytes, but recommended alignment is %2 bytes") - << Struct << (int)CurrAlign.getQuantity() << NewAlignQuantity << FixIt; + << Struct << CurrAlign.getQuantity() << NewAlignQuantity << FixIt; diag(Struct->getLocation(), "use \"__attribute__((aligned(%0)))\" to align struct %1 to %0 bytes", diff --git a/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp b/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp index b0cd4cdb41e9..c759e5b5ee7b 100644 --- a/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp +++ b/clang-tools-extra/clang-tidy/altera/UnrollLoopsCheck.cpp @@ -208,20 +208,22 @@ bool UnrollLoopsCheck::hasLargeNumIterations(const Stmt *Statement, return true; switch (Op->getOpcode()) { case (BO_AddAssign): - Iterations = std::ceil(float(EndValue - InitValue) / ConstantValue); + Iterations = + std::ceil(static_cast(EndValue - InitValue) / ConstantValue); break; case (BO_SubAssign): - Iterations = std::ceil(float(InitValue - EndValue) / ConstantValue); + Iterations = + std::ceil(static_cast(InitValue - EndValue) / ConstantValue); break; case (BO_MulAssign): - Iterations = - 1 + ((std::log((double)EndValue) - std::log((double)InitValue)) / - std::log((double)ConstantValue)); + Iterations = 1 + ((std::log(static_cast(EndValue)) - + std::log(static_cast(InitValue))) / + std::log(static_cast(ConstantValue))); break; case (BO_DivAssign): - Iterations = - 1 + ((std::log((double)InitValue) - std::log((double)EndValue)) / - std::log((double)ConstantValue)); + Iterations = 1 + ((std::log(static_cast(InitValue)) - + std::log(static_cast(EndValue))) / + std::log(static_cast(ConstantValue))); break; default: // All other operators are not handled; assume large bounds. diff --git a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp index 103b403b9fe5..6618341296aa 100644 --- a/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp @@ -75,12 +75,9 @@ static bool isFallthroughSwitchBranch(const SwitchBranch &Branch) { if (!S) return true; - for (const Attr *A : S->getAttrs()) { - if (isa(A)) - return false; - } - - return true; + return llvm::all_of(S->getAttrs(), [](const Attr *A) { + return !isa(A); + }); } } Visitor; @@ -240,6 +237,12 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, return false; return true; } + case Stmt::DeferStmtClass: { + const auto *DefStmt1 = cast(Stmt1); + const auto *DefStmt2 = cast(Stmt2); + return isIdenticalStmt(Ctx, DefStmt1->getBody(), DefStmt2->getBody(), + IgnoreSideEffects); + } case Stmt::CompoundStmtClass: { const auto *CompStmt1 = cast(Stmt1); const auto *CompStmt2 = cast(Stmt2); diff --git a/clang-tools-extra/clang-tidy/bugprone/CapturingThisInMemberVariableCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/CapturingThisInMemberVariableCheck.cpp index a376de505dd7..6aed454813a2 100644 --- a/clang-tools-extra/clang-tidy/bugprone/CapturingThisInMemberVariableCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/CapturingThisInMemberVariableCheck.cpp @@ -44,18 +44,17 @@ AST_MATCHER(CXXRecordDecl, correctHandleCaptureThisLambda) { if (Node.hasSimpleMoveAssignment()) return false; - for (const CXXConstructorDecl *C : Node.ctors()) { - if (C->isCopyOrMoveConstructor() && C->isDefaulted() && !C->isDeleted()) - return false; - } - for (const CXXMethodDecl *M : Node.methods()) { - if (M->isCopyAssignmentOperator()) - llvm::errs() << M->isDeleted() << "\n"; - if (M->isCopyAssignmentOperator() && M->isDefaulted() && !M->isDeleted()) - return false; - if (M->isMoveAssignmentOperator() && M->isDefaulted() && !M->isDeleted()) - return false; - } + if (llvm::any_of(Node.ctors(), [](const CXXConstructorDecl *C) { + return C->isCopyOrMoveConstructor() && C->isDefaulted() && + !C->isDeleted(); + })) + return false; + if (llvm::any_of(Node.methods(), [](const CXXMethodDecl *M) { + return (M->isCopyAssignmentOperator() || + M->isMoveAssignmentOperator()) && + M->isDefaulted() && !M->isDeleted(); + })) + return false; // FIXME: find ways to identifier correct handle capture this lambda return true; } diff --git a/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp index 3d3fc785b71f..47acc217b2c2 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ChainedComparisonCheck.cpp @@ -11,7 +11,6 @@ #include "clang/ASTMatchers/ASTMatchFinder.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" -#include using namespace clang::ast_matchers; diff --git a/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.cpp index 59cc565c0ff1..c95ad2beed3a 100644 --- a/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.cpp @@ -7,13 +7,11 @@ //===----------------------------------------------------------------------===// #include "DanglingHandleCheck.h" -#include "../utils/Matchers.h" #include "../utils/OptionsUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" using namespace clang::ast_matchers; -using namespace clang::tidy::matchers; namespace clang::tidy::bugprone { diff --git a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp index a07a68c8a3e6..496f3e501599 100644 --- a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp @@ -1589,11 +1589,9 @@ static bool lazyMapOfSetsIntersectionExists(const MapTy &Map, const ElemTy &E1, if (E1Iterator == Map.end() || E2Iterator == Map.end()) return false; - for (const auto &E1SetElem : E1Iterator->second) - if (E2Iterator->second.contains(E1SetElem)) - return true; - - return false; + return llvm::any_of(E1Iterator->second, [&E2Iterator](const auto &E1SetElem) { + return E2Iterator->second.contains(E1SetElem); + }); } /// Implements the heuristic that marks two parameters related if there is diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp index b7de8395ffa0..1cfb1511fa94 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -72,7 +72,8 @@ void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { auto MatchIf = [](bool Enabled, const auto &Matcher) { - ast_matchers::internal::Matcher Nothing = unless(anything()); + const ast_matchers::internal::Matcher Nothing = + unless(anything()); return Enabled ? Matcher : Nothing; }; Finder->addMatcher( diff --git a/clang-tools-extra/clang-tidy/bugprone/FloatLoopCounterCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/FloatLoopCounterCheck.cpp index adf2d2b4bcc0..38a023433775 100644 --- a/clang-tools-extra/clang-tidy/bugprone/FloatLoopCounterCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/FloatLoopCounterCheck.cpp @@ -31,6 +31,7 @@ void FloatLoopCounterCheck::registerMatchers(MatchFinder *Finder) { void FloatLoopCounterCheck::check(const MatchFinder::MatchResult &Result) { const auto *FS = Result.Nodes.getNodeAs("for"); + assert(FS && "FS should not be null"); diag(FS->getInc()->getBeginLoc(), "loop induction expression should not have " "floating-point type") diff --git a/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp index 50280d22be0d..6749c59d5fd5 100644 --- a/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/InfiniteLoopCheck.cpp @@ -119,14 +119,9 @@ static bool isAtLeastOneCondVarChanged(const Decl *Func, const Stmt *LoopStmt, if (isVarThatIsPossiblyChanged(Func, LoopStmt, Cond, Context)) return true; - for (const Stmt *Child : Cond->children()) { - if (!Child) - continue; - - if (isAtLeastOneCondVarChanged(Func, LoopStmt, Child, Context)) - return true; - } - return false; + return llvm::any_of(Cond->children(), [&](const Stmt *Child) { + return Child && isAtLeastOneCondVarChanged(Func, LoopStmt, Child, Context); + }); } /// Return the variable names in `Cond`. @@ -240,10 +235,9 @@ static bool hasStaticLocalVariable(const Stmt *Cond) { return true; } - for (const Stmt *Child : Cond->children()) - if (Child && hasStaticLocalVariable(Child)) - return true; - return false; + return llvm::any_of(Cond->children(), [](const Stmt *Child) { + return Child && hasStaticLocalVariable(Child); + }); } /// Tests if the loop condition `Cond` involves static local variables and diff --git a/clang-tools-extra/clang-tidy/bugprone/NonZeroEnumToBoolConversionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NonZeroEnumToBoolConversionCheck.cpp index 067577f18428..127af2725cf1 100644 --- a/clang-tools-extra/clang-tidy/bugprone/NonZeroEnumToBoolConversionCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/NonZeroEnumToBoolConversionCheck.cpp @@ -11,7 +11,6 @@ #include "../utils/OptionsUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" -#include using namespace clang::ast_matchers; diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp index cf8bc9794d9c..4f0d819d2147 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousMissingCommaCheck.cpp @@ -116,7 +116,7 @@ void SuspiciousMissingCommaCheck::check( // Warn only when concatenation is not common in this initializer list. // The current threshold is set to less than 1/5 of the string literals. - if (double(Count) / Size > RatioThreshold) + if (static_cast(Count) / Size > RatioThreshold) return; diag(ConcatenatedLiteral->getBeginLoc(), diff --git a/clang-tools-extra/clang-tidy/bugprone/SuspiciousReallocUsageCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SuspiciousReallocUsageCheck.cpp index 7cc3630204e6..bf31218131d5 100644 --- a/clang-tools-extra/clang-tidy/bugprone/SuspiciousReallocUsageCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousReallocUsageCheck.cpp @@ -92,10 +92,9 @@ class FindAssignToVarBefore return false; } bool VisitStmt(const Stmt *S) { - for (const Stmt *Child : S->children()) - if (Child && Visit(Child)) - return true; - return false; + return llvm::any_of(S->children(), [this](const Stmt *Child) { + return Child && Visit(Child); + }); } }; diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp index 67d0931003c5..0a7467a0ea65 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp @@ -266,8 +266,8 @@ void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) { } void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) { - const Expr *SourceExpr; - const FunctionDecl *FuncDecl; + const Expr *SourceExpr = nullptr; + const FunctionDecl *FuncDecl = nullptr; if (const auto *DeclRef = Result.Nodes.getNodeAs(DeclRefId)) { SourceExpr = DeclRef; diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp index 6d134a0e896a..b2e08fe688a1 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp @@ -19,6 +19,7 @@ #include "../utils/ExprSequence.h" #include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" #include using namespace clang::ast_matchers; @@ -48,7 +49,8 @@ struct UseAfterMove { /// various internal helper functions). class UseAfterMoveFinder { public: - UseAfterMoveFinder(ASTContext *TheContext); + UseAfterMoveFinder(ASTContext *TheContext, + llvm::ArrayRef InvalidationFunctions); // Within the given code block, finds the first use of 'MovedVariable' that // occurs after 'MovingCall' (the expression that performs the move). If a @@ -71,6 +73,7 @@ class UseAfterMoveFinder { llvm::SmallPtrSetImpl *DeclRefs); ASTContext *Context; + llvm::ArrayRef InvalidationFunctions; std::unique_ptr Sequence; std::unique_ptr BlockMap; llvm::SmallPtrSet Visited; @@ -78,6 +81,11 @@ class UseAfterMoveFinder { } // namespace +static auto getNameMatcher(llvm::ArrayRef InvalidationFunctions) { + return anyOf(hasAnyName("::std::move", "::std::forward"), + matchers::matchesAnyListedName(InvalidationFunctions)); +} + // Matches nodes that are // - Part of a decltype argument or class template argument (we check this by // seeing if they are children of a TypeLoc), or @@ -92,8 +100,9 @@ static StatementMatcher inDecltypeOrTemplateArg() { hasAncestor(expr(hasUnevaluatedContext()))); } -UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext) - : Context(TheContext) {} +UseAfterMoveFinder::UseAfterMoveFinder( + ASTContext *TheContext, llvm::ArrayRef InvalidationFunctions) + : Context(TheContext), InvalidationFunctions(InvalidationFunctions) {} std::optional UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall, @@ -359,7 +368,7 @@ void UseAfterMoveFinder::getReinits( unless(parmVarDecl(hasType( references(qualType(isConstQualified())))))), unless(callee(functionDecl( - hasAnyName("::std::move", "::std::forward"))))))) + getNameMatcher(InvalidationFunctions))))))) .bind("reinit"); Stmts->clear(); @@ -388,18 +397,21 @@ void UseAfterMoveFinder::getReinits( } } -enum class MoveType { - Move, // std::move - Forward, // std::forward +enum MoveType { + Forward = 0, // std::forward + Move = 1, // std::move + Invalidation = 2, // other }; static MoveType determineMoveType(const FunctionDecl *FuncDecl) { - if (FuncDecl->getName() == "move") - return MoveType::Move; - if (FuncDecl->getName() == "forward") - return MoveType::Forward; + if (FuncDecl->isInStdNamespace()) { + if (FuncDecl->getName() == "move") + return MoveType::Move; + if (FuncDecl->getName() == "forward") + return MoveType::Forward; + } - llvm_unreachable("Invalid move type"); + return MoveType::Invalidation; } static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg, @@ -408,29 +420,38 @@ static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg, const SourceLocation UseLoc = Use.DeclRef->getExprLoc(); const SourceLocation MoveLoc = MovingCall->getExprLoc(); - const bool IsMove = (Type == MoveType::Move); - - Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1") - << MoveArg->getDecl()->getName() << IsMove; - Check->diag(MoveLoc, "%select{forward|move}0 occurred here", + Check->diag(UseLoc, + "'%0' used after it was %select{forwarded|moved|invalidated}1") + << MoveArg->getDecl()->getName() << Type; + Check->diag(MoveLoc, "%select{forward|move|invalidation}0 occurred here", DiagnosticIDs::Note) - << IsMove; + << Type; if (Use.EvaluationOrderUndefined) { Check->diag( UseLoc, - "the use and %select{forward|move}0 are unsequenced, i.e. " + "the use and %select{forward|move|invalidation}0 are unsequenced, i.e. " "there is no guarantee about the order in which they are evaluated", DiagnosticIDs::Note) - << IsMove; + << Type; } else if (Use.UseHappensInLaterLoopIteration) { Check->diag(UseLoc, "the use happens in a later loop iteration than the " - "%select{forward|move}0", + "%select{forward|move|invalidation}0", DiagnosticIDs::Note) - << IsMove; + << Type; } } +UseAfterMoveCheck::UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + InvalidationFunctions(utils::options::parseStringList( + Options.get("InvalidationFunctions", ""))) {} + +void UseAfterMoveCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "InvalidationFunctions", + utils::options::serializeStringList(InvalidationFunctions)); +} + void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { // try_emplace is a common maybe-moving function that returns a // bool to tell callers whether it moved. Ignore std::move inside @@ -438,11 +459,14 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { // the bool. auto TryEmplaceMatcher = cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace")))); + auto Arg = declRefExpr().bind("arg"); + auto IsMemberCallee = callee(functionDecl(unless(isStaticStorageClass()))); auto CallMoveMatcher = - callExpr(argumentCountIs(1), - callee(functionDecl(hasAnyName("::std::move", "::std::forward")) + callExpr(callee(functionDecl(getNameMatcher(InvalidationFunctions)) .bind("move-decl")), - hasArgument(0, declRefExpr().bind("arg")), + anyOf(cxxMemberCallExpr(IsMemberCallee, on(Arg)), + callExpr(unless(cxxMemberCallExpr(IsMemberCallee)), + hasArgument(0, Arg))), unless(inDecltypeOrTemplateArg()), unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"), anyOf(hasAncestor(compoundStmt( @@ -521,7 +545,7 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) { } for (Stmt *CodeBlock : CodeBlocks) { - UseAfterMoveFinder Finder(Result.Context); + UseAfterMoveFinder Finder(Result.Context, InvalidationFunctions); if (auto Use = Finder.find(CodeBlock, MovingCall, Arg)) emitDiagnostic(MovingCall, Arg, *Use, this, Result.Context, determineMoveType(MoveDecl)); diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.h b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.h index d38b29e09fa8..1bbf5c00785f 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.h @@ -20,13 +20,16 @@ namespace clang::tidy::bugprone { /// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html class UseAfterMoveCheck : public ClangTidyCheck { public: - UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} + UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { return LangOpts.CPlusPlus11; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::vector InvalidationFunctions; }; } // namespace clang::tidy::bugprone diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp index d0f86526d1a2..1c5c854cb4d8 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp @@ -19,10 +19,11 @@ namespace clang::tidy::cppcoreguidelines { namespace { AST_MATCHER_P(CXXForRangeStmt, hasRangeBeginEndStmt, ast_matchers::internal::Matcher, InnerMatcher) { - for (const DeclStmt *Stmt : {Node.getBeginStmt(), Node.getEndStmt()}) - if (Stmt != nullptr && InnerMatcher.matches(*Stmt, Finder, Builder)) - return true; - return false; + return llvm::any_of(llvm::ArrayRef{Node.getBeginStmt(), Node.getEndStmt()}, + [&](const DeclStmt *Stmt) { + return Stmt && + InnerMatcher.matches(*Stmt, Finder, Builder); + }); } AST_MATCHER(Stmt, isInsideOfRangeBeginEndStmt) { diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccessCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccessCheck.cpp index 54c469292394..cf4b445a554e 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccessCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccessCheck.cpp @@ -176,7 +176,7 @@ void ProBoundsAvoidUncheckedContainerAccessCheck::check( } } else if (const auto *MCE = dyn_cast(MatchedExpr)) { // Case: a.operator[](i) or a->operator[](i) - const auto *Callee = dyn_cast(MCE->getCallee()); + const auto *Callee = cast(MCE->getCallee()); if (FixMode == At) { // Cases: a.operator[](i) => a.at(i) and a->operator[](i) => a->at(i) diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp index 66508da89f0d..f2676468b687 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProTypeMemberInitCheck.cpp @@ -361,7 +361,8 @@ void ProTypeMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { } // FIXME: Copied from clang/lib/Sema/SemaDeclCXX.cpp. -static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) { +static bool isIncompleteOrZeroLengthArrayType(const ASTContext &Context, + QualType T) { if (T->isIncompleteArrayType()) return true; @@ -375,7 +376,7 @@ static bool isIncompleteOrZeroLengthArrayType(ASTContext &Context, QualType T) { return false; } -static bool isEmpty(ASTContext &Context, const QualType &Type) { +static bool isEmpty(const ASTContext &Context, const QualType &Type) { if (const CXXRecordDecl *ClassDecl = Type->getAsCXXRecordDecl()) { return ClassDecl->isEmpty(); } @@ -431,19 +432,13 @@ static llvm::StringLiteral getInitializer(QualType QT, bool UseAssignment) { } } -void ProTypeMemberInitCheck::checkMissingMemberInitializer( - ASTContext &Context, const CXXRecordDecl &ClassDecl, - const CXXConstructorDecl *Ctor) { - const bool IsUnion = ClassDecl.isUnion(); - - if (IsUnion && ClassDecl.hasInClassInitializer()) - return; - - // Gather all fields (direct and indirect) that need to be initialized. - SmallPtrSet FieldsToInit; +static void +computeFieldsToInit(const ASTContext &Context, const RecordDecl &Record, + bool IgnoreArrays, + SmallPtrSetImpl &FieldsToInit) { bool AnyMemberHasInitPerUnion = false; forEachFieldWithFilter( - ClassDecl, ClassDecl.fields(), AnyMemberHasInitPerUnion, + Record, Record.fields(), AnyMemberHasInitPerUnion, [&](const FieldDecl *F) { if (IgnoreArrays && F->getType()->isArrayType()) return; @@ -458,6 +453,19 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer( !AnyMemberHasInitPerUnion) FieldsToInit.insert(F); }); +} + +void ProTypeMemberInitCheck::checkMissingMemberInitializer( + ASTContext &Context, const CXXRecordDecl &ClassDecl, + const CXXConstructorDecl *Ctor) { + const bool IsUnion = ClassDecl.isUnion(); + + if (IsUnion && ClassDecl.hasInClassInitializer()) + return; + + // Gather all fields (direct and indirect) that need to be initialized. + SmallPtrSet FieldsToInit; + computeFieldsToInit(Context, ClassDecl, IgnoreArrays, FieldsToInit); if (FieldsToInit.empty()) return; @@ -507,7 +515,7 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer( // Collect all fields but only suggest a fix for the first member of unions, // as initializing more than one union member is an error. SmallPtrSet FieldsToFix; - AnyMemberHasInitPerUnion = false; + bool AnyMemberHasInitPerUnion = false; forEachFieldWithFilter(ClassDecl, ClassDecl.fields(), AnyMemberHasInitPerUnion, [&](const FieldDecl *F) { if (!FieldsToInit.contains(F)) @@ -582,6 +590,17 @@ void ProTypeMemberInitCheck::checkMissingBaseClassInitializer( void ProTypeMemberInitCheck::checkUninitializedTrivialType( const ASTContext &Context, const VarDecl *Var) { + // Verify that the record actually needs initialization + const CXXRecordDecl *Record = Var->getType()->getAsCXXRecordDecl(); + if (!Record) + return; + + SmallPtrSet FieldsToInit; + computeFieldsToInit(Context, *Record, IgnoreArrays, FieldsToInit); + + if (FieldsToInit.empty()) + return; + const DiagnosticBuilder Diag = diag(Var->getBeginLoc(), "uninitialized record type: %0") << Var; diff --git a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp index 652dec9bcc2a..4a10cb4085a4 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.cpp @@ -17,113 +17,61 @@ namespace clang::tidy::fuchsia { namespace { AST_MATCHER(CXXRecordDecl, hasBases) { - if (Node.hasDefinition()) - return Node.getNumBases() > 0; - return false; + return Node.hasDefinition() && Node.getNumBases() > 0; } } // namespace -// Adds a node (by name) to the interface map, if it was not present in the map -// previously. -void MultipleInheritanceCheck::addNodeToInterfaceMap(const CXXRecordDecl *Node, - bool IsInterface) { - assert(Node->getIdentifier()); - const StringRef Name = Node->getIdentifier()->getName(); - InterfaceMap.insert(std::make_pair(Name, IsInterface)); -} - -// Returns "true" if the boolean "isInterface" has been set to the -// interface status of the current Node. Return "false" if the -// interface status for the current node is not yet known. -bool MultipleInheritanceCheck::getInterfaceStatus(const CXXRecordDecl *Node, - bool &IsInterface) const { - assert(Node->getIdentifier()); - const StringRef Name = Node->getIdentifier()->getName(); - auto Pair = InterfaceMap.find(Name); - if (Pair == InterfaceMap.end()) - return false; - IsInterface = Pair->second; - return true; -} - -bool MultipleInheritanceCheck::isCurrentClassInterface( - const CXXRecordDecl *Node) const { - // Interfaces should have no fields. - if (!Node->field_empty()) - return false; - - // Interfaces should have exclusively pure methods. - return llvm::none_of(Node->methods(), [](const CXXMethodDecl *M) { - return M->isUserProvided() && !M->isPureVirtual() && !M->isStatic(); - }); -} +bool MultipleInheritanceCheck::isInterface(const CXXBaseSpecifier &Base) { + const CXXRecordDecl *const Node = Base.getType()->getAsCXXRecordDecl(); + if (!Node) + return true; -bool MultipleInheritanceCheck::isInterface(const CXXRecordDecl *Node) { - if (!Node->getIdentifier()) - return false; + assert(Node->isCompleteDefinition()); // Short circuit the lookup if we have analyzed this record before. - bool PreviousIsInterfaceResult = false; - if (getInterfaceStatus(Node, PreviousIsInterfaceResult)) - return PreviousIsInterfaceResult; - - // To be an interface, all base classes must be interfaces as well. - for (const auto &I : Node->bases()) { - if (I.isVirtual()) - continue; - const auto *Base = I.getType()->getAsCXXRecordDecl(); - if (!Base) - continue; - assert(Base->isCompleteDefinition()); - if (!isInterface(Base)) { - addNodeToInterfaceMap(Node, false); - return false; - } - } - - const bool CurrentClassIsInterface = isCurrentClassInterface(Node); - addNodeToInterfaceMap(Node, CurrentClassIsInterface); + if (const auto CachedValue = InterfaceMap.find(Node); + CachedValue != InterfaceMap.end()) + return CachedValue->second; + + // To be an interface, a class must have... + const bool CurrentClassIsInterface = + // ...no bases that aren't interfaces... + llvm::none_of(Node->bases(), + [&](const CXXBaseSpecifier &I) { + return !I.isVirtual() && !isInterface(I); + }) && + // ...no fields, and... + Node->field_empty() && + // ...no methods that aren't pure virtual. + llvm::none_of(Node->methods(), [](const CXXMethodDecl *M) { + return M->isUserProvided() && !M->isPureVirtual() && !M->isStatic(); + }); + + InterfaceMap.try_emplace(Node, CurrentClassIsInterface); return CurrentClassIsInterface; } void MultipleInheritanceCheck::registerMatchers(MatchFinder *Finder) { - // Match declarations which have bases. Finder->addMatcher(cxxRecordDecl(hasBases(), isDefinition()).bind("decl"), this); } void MultipleInheritanceCheck::check(const MatchFinder::MatchResult &Result) { - if (const auto *D = Result.Nodes.getNodeAs("decl")) { - // Check against map to see if the class inherits from multiple - // concrete classes - unsigned NumConcrete = 0; - for (const auto &I : D->bases()) { - if (I.isVirtual()) - continue; - const auto *Base = I.getType()->getAsCXXRecordDecl(); - if (!Base) - continue; - assert(Base->isCompleteDefinition()); - if (!isInterface(Base)) - NumConcrete++; - } - - // Check virtual bases to see if there is more than one concrete - // non-virtual base. - for (const auto &V : D->vbases()) { - const auto *Base = V.getType()->getAsCXXRecordDecl(); - if (!Base) - continue; - assert(Base->isCompleteDefinition()); - if (!isInterface(Base)) - NumConcrete++; - } - - if (NumConcrete > 1) { - diag(D->getBeginLoc(), "inheriting multiple classes that aren't " - "pure virtual is discouraged"); - } - } + const auto &D = *Result.Nodes.getNodeAs("decl"); + // Check to see if the class inherits from multiple concrete classes. + unsigned NumConcrete = + llvm::count_if(D.bases(), [&](const CXXBaseSpecifier &I) { + return !I.isVirtual() && !isInterface(I); + }); + + // Check virtual bases to see if there is more than one concrete + // non-virtual base. + NumConcrete += llvm::count_if( + D.vbases(), [&](const CXXBaseSpecifier &V) { return !isInterface(V); }); + + if (NumConcrete > 1) + diag(D.getBeginLoc(), "inheriting multiple classes that aren't " + "pure virtual is discouraged"); } } // namespace clang::tidy::fuchsia diff --git a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.h b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.h index 2e268432c17c..4dcbd0c7893c 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.h +++ b/clang-tools-extra/clang-tidy/fuchsia/MultipleInheritanceCheck.h @@ -30,15 +30,12 @@ class MultipleInheritanceCheck : public ClangTidyCheck { void onEndOfTranslationUnit() override { InterfaceMap.clear(); } private: - void addNodeToInterfaceMap(const CXXRecordDecl *Node, bool IsInterface); - bool getInterfaceStatus(const CXXRecordDecl *Node, bool &IsInterface) const; - bool isCurrentClassInterface(const CXXRecordDecl *Node) const; - bool isInterface(const CXXRecordDecl *Node); + bool isInterface(const CXXBaseSpecifier &Base); // Contains the identity of each named CXXRecord as an interface. This is // used to memoize lookup speeds and improve performance from O(N^2) to O(N), // where N is the number of classes. - llvm::StringMap InterfaceMap; + llvm::DenseMap InterfaceMap; }; } // namespace clang::tidy::fuchsia diff --git a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp index b6fb22c66d37..9c98b4938844 100644 --- a/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp +++ b/clang-tools-extra/clang-tidy/fuchsia/VirtualInheritanceCheck.cpp @@ -20,10 +20,9 @@ AST_MATCHER(CXXRecordDecl, hasDirectVirtualBaseClass) { return false; if (!Node.getNumVBases()) return false; - for (const CXXBaseSpecifier &Base : Node.bases()) - if (Base.isVirtual()) - return true; - return false; + return llvm::any_of(Node.bases(), [](const CXXBaseSpecifier &Base) { + return Base.isVirtual(); + }); } } // namespace diff --git a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp deleted file mode 100644 index c438889e22ab..000000000000 --- a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp +++ /dev/null @@ -1,291 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 "AvoidCStyleCastsCheck.h" -#include "clang/AST/ASTContext.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Lex/Lexer.h" - -using namespace clang::ast_matchers; - -namespace clang::tidy::google::readability { - -void AvoidCStyleCastsCheck::registerMatchers( - ast_matchers::MatchFinder *Finder) { - Finder->addMatcher( - cStyleCastExpr( - // Filter out (EnumType)IntegerLiteral construct, which is generated - // for non-type template arguments of enum types. - // FIXME: Remove this once this is fixed in the AST. - unless(hasParent(substNonTypeTemplateParmExpr()))) - .bind("cast"), - this); - - Finder->addMatcher( - cxxFunctionalCastExpr( - hasDestinationType(hasCanonicalType(anyOf( - builtinType(), references(qualType()), pointsTo(qualType())))), - unless( - hasSourceExpression(anyOf(cxxConstructExpr(), initListExpr())))) - .bind("cast"), - this); -} - -static bool needsConstCast(QualType SourceType, QualType DestType) { - while ((SourceType->isPointerType() && DestType->isPointerType()) || - (SourceType->isReferenceType() && DestType->isReferenceType())) { - SourceType = SourceType->getPointeeType(); - DestType = DestType->getPointeeType(); - if (SourceType.isConstQualified() && !DestType.isConstQualified()) { - return (SourceType->isPointerType() == DestType->isPointerType()) && - (SourceType->isReferenceType() == DestType->isReferenceType()); - } - } - return false; -} - -static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) { - while ((T1->isPointerType() && T2->isPointerType()) || - (T1->isReferenceType() && T2->isReferenceType())) { - T1 = T1->getPointeeType(); - T2 = T2->getPointeeType(); - } - return T1.getUnqualifiedType() == T2.getUnqualifiedType(); -} - -static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr) { - if (const auto *CastExpr = dyn_cast(Expr)) - return CharSourceRange::getCharRange( - CastExpr->getLParenLoc(), - CastExpr->getSubExprAsWritten()->getBeginLoc()); - if (const auto *CastExpr = dyn_cast(Expr)) - return CharSourceRange::getCharRange(CastExpr->getBeginLoc(), - CastExpr->getLParenLoc()); - llvm_unreachable("Unsupported CastExpr"); -} - -static StringRef getDestTypeString(const SourceManager &SM, - const LangOptions &LangOpts, - const ExplicitCastExpr *Expr) { - SourceLocation BeginLoc; - SourceLocation EndLoc; - - if (const auto *CastExpr = dyn_cast(Expr)) { - BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(1); - EndLoc = CastExpr->getRParenLoc().getLocWithOffset(-1); - } else if (const auto *CastExpr = dyn_cast(Expr)) { - BeginLoc = CastExpr->getBeginLoc(); - EndLoc = CastExpr->getLParenLoc().getLocWithOffset(-1); - } else - llvm_unreachable("Unsupported CastExpr"); - - return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), - SM, LangOpts); -} - -static bool sameTypeAsWritten(QualType X, QualType Y) { - if (X.getCanonicalType() != Y.getCanonicalType()) - return false; - - auto TC = X->getTypeClass(); - if (TC != Y->getTypeClass()) - return false; - - switch (TC) { - case Type::Typedef: - return declaresSameEntity(cast(X)->getDecl(), - cast(Y)->getDecl()); - case Type::Pointer: - return sameTypeAsWritten(cast(X)->getPointeeType(), - cast(Y)->getPointeeType()); - case Type::RValueReference: - case Type::LValueReference: - return sameTypeAsWritten(cast(X)->getPointeeType(), - cast(Y)->getPointeeType()); - default: - return true; - } -} - -void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) { - const auto *CastExpr = Result.Nodes.getNodeAs("cast"); - - // Ignore casts in macros. - if (CastExpr->getExprLoc().isMacroID()) - return; - - // Casting to void is an idiomatic way to mute "unused variable" and similar - // warnings. - if (CastExpr->getCastKind() == CK_ToVoid) - return; - - auto IsFunction = [](QualType T) { - T = T.getCanonicalType().getNonReferenceType(); - return T->isFunctionType() || T->isFunctionPointerType() || - T->isMemberFunctionPointerType(); - }; - - const QualType DestTypeAsWritten = - CastExpr->getTypeAsWritten().getUnqualifiedType(); - const QualType SourceTypeAsWritten = - CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType(); - const QualType SourceType = SourceTypeAsWritten.getCanonicalType(); - const QualType DestType = DestTypeAsWritten.getCanonicalType(); - - CharSourceRange ReplaceRange = getReplaceRange(CastExpr); - - const bool FnToFnCast = - IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten); - - const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() && - DestTypeAsWritten->isRecordType() && - !DestTypeAsWritten->isElaboratedTypeSpecifier(); - - if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { - // Function pointer/reference casts may be needed to resolve ambiguities in - // case of overloaded functions, so detection of redundant casts is trickier - // in this case. Don't emit "redundant cast" warnings for function - // pointer/reference types. - if (sameTypeAsWritten(SourceTypeAsWritten, DestTypeAsWritten)) { - diag(CastExpr->getBeginLoc(), "redundant cast to the same type") - << FixItHint::CreateRemoval(ReplaceRange); - return; - } - } - - // The rest of this check is only relevant to C++. - // We also disable it for Objective-C++. - if (!getLangOpts().CPlusPlus || getLangOpts().ObjC) - return; - // Ignore code inside extern "C" {} blocks. - if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) - .empty()) - return; - // Ignore code in .c files and headers included from them, even if they are - // compiled as C++. - if (getCurrentMainFile().ends_with(".c")) - return; - - SourceManager &SM = *Result.SourceManager; - - // Ignore code in .c files #included in other files (which shouldn't be done, - // but people still do this for test and other purposes). - if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())) - .ends_with(".c")) - return; - - // Leave type spelling exactly as it was (unlike - // getTypeAsWritten().getAsString() which would spell enum types 'enum X'). - StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr); - - auto Diag = - diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0"); - - auto ReplaceWithCast = [&](std::string CastText) { - const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); - if (!isa(SubExpr) && !isa(CastExpr)) { - CastText.push_back('('); - Diag << FixItHint::CreateInsertion( - Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM, - getLangOpts()), - ")"); - } - Diag << FixItHint::CreateReplacement(ReplaceRange, CastText); - }; - auto ReplaceWithNamedCast = [&](StringRef CastType) { - Diag << CastType; - ReplaceWithCast((CastType + "<" + DestTypeString + ">").str()); - }; - auto ReplaceWithConstructorCall = [&]() { - Diag << "constructor call syntax"; - // FIXME: Validate DestTypeString, maybe. - ReplaceWithCast(DestTypeString.str()); - }; - // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. - switch (CastExpr->getCastKind()) { - case CK_FunctionToPointerDecay: - ReplaceWithNamedCast("static_cast"); - return; - case CK_ConstructorConversion: - if (ConstructorCast) { - ReplaceWithConstructorCall(); - } else { - ReplaceWithNamedCast("static_cast"); - } - return; - case CK_NoOp: - if (FnToFnCast) { - ReplaceWithNamedCast("static_cast"); - return; - } - if (SourceType == DestType) { - Diag << "static_cast (if needed, the cast may be redundant)"; - ReplaceWithCast(("static_cast<" + DestTypeString + ">").str()); - return; - } - if (needsConstCast(SourceType, DestType) && - pointedUnqualifiedTypesAreEqual(SourceType, DestType)) { - ReplaceWithNamedCast("const_cast"); - return; - } - if (ConstructorCast) { - ReplaceWithConstructorCall(); - return; - } - if (DestType->isReferenceType()) { - const QualType Dest = DestType.getNonReferenceType(); - const QualType Source = SourceType.getNonReferenceType(); - if (Source == Dest.withConst() || - SourceType.getNonReferenceType() == DestType.getNonReferenceType()) { - ReplaceWithNamedCast("const_cast"); - return; - } - break; - } - if (DestType->isVoidPointerType() && SourceType->isPointerType() && - !SourceType->getPointeeType()->isPointerType()) { - ReplaceWithNamedCast("reinterpret_cast"); - return; - } - - [[fallthrough]]; - case clang::CK_IntegralCast: - // Convert integral and no-op casts between builtin types and enums to - // static_cast. A cast from enum to integer may be unnecessary, but it's - // still retained. - if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) && - (DestType->isBuiltinType() || DestType->isEnumeralType())) { - ReplaceWithNamedCast("static_cast"); - return; - } - break; - case CK_BitCast: - // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. - if (!needsConstCast(SourceType, DestType)) { - if (SourceType->isVoidPointerType()) - ReplaceWithNamedCast("static_cast"); - else - ReplaceWithNamedCast("reinterpret_cast"); - return; - } - break; - case CK_BaseToDerived: - if (!needsConstCast(SourceType, DestType)) { - ReplaceWithNamedCast("static_cast"); - return; - } - break; - default: - break; - } - - Diag << "static_cast/const_cast/reinterpret_cast"; -} - -} // namespace clang::tidy::google::readability diff --git a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.h b/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.h deleted file mode 100644 index a305bd524aef..000000000000 --- a/clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.h +++ /dev/null @@ -1,41 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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_GOOGLE_AVOIDCSTYLECASTSCHECK_H -#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H - -#include "../ClangTidyCheck.h" - -namespace clang::tidy::google::readability { - -/// Finds usages of C-style casts. -/// -/// https://google.github.io/styleguide/cppguide.html#Casting -/// -/// Corresponding cpplint.py check name: 'readability/casting'. -/// -/// This check is similar to `-Wold-style-cast`, but it suggests automated fixes -/// in some cases. The reported locations should not be different from the -/// ones generated by `-Wold-style-cast`. -/// -/// For the user-facing documentation see: -/// https://clang.llvm.org/extra/clang-tidy/checks/google/readability-casting.html -class AvoidCStyleCastsCheck : public ClangTidyCheck { -public: - AvoidCStyleCastsCheck(StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context) {} - void registerMatchers(ast_matchers::MatchFinder *Finder) override; - void check(const ast_matchers::MatchFinder::MatchResult &Result) override; - std::optional getCheckTraversalKind() const override { - return TK_IgnoreUnlessSpelledInSource; - } -}; - -} // namespace clang::tidy::google::readability - -#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_AVOIDCSTYLECASTSCHECK_H diff --git a/clang-tools-extra/clang-tidy/google/CMakeLists.txt b/clang-tools-extra/clang-tidy/google/CMakeLists.txt index 1d4229ebb7b0..982a188e8e80 100644 --- a/clang-tools-extra/clang-tidy/google/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/google/CMakeLists.txt @@ -4,7 +4,6 @@ set(LLVM_LINK_COMPONENTS ) add_clang_library(clangTidyGoogleModule STATIC - AvoidCStyleCastsCheck.cpp AvoidNSObjectNewCheck.cpp AvoidThrowingObjCExceptionCheck.cpp AvoidUnderscoreInGoogletestNameCheck.cpp @@ -25,6 +24,7 @@ add_clang_library(clangTidyGoogleModule STATIC LINK_LIBS clangTidy + clangTidyModernizeModule clangTidyReadabilityModule clangTidyUtils diff --git a/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp b/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp index aff8b45ff2f7..ce46b3f64179 100644 --- a/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/google/GoogleTidyModule.cpp @@ -9,10 +9,10 @@ #include "../ClangTidy.h" #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" +#include "../modernize/AvoidCStyleCastCheck.h" #include "../readability/BracesAroundStatementsCheck.h" #include "../readability/FunctionSizeCheck.h" #include "../readability/NamespaceCommentCheck.h" -#include "AvoidCStyleCastsCheck.h" #include "AvoidNSObjectNewCheck.h" #include "AvoidThrowingObjCExceptionCheck.h" #include "AvoidUnderscoreInGoogletestNameCheck.h" @@ -67,7 +67,7 @@ class GoogleModule : public ClangTidyModule { CheckFactories .registerCheck( "google-readability-avoid-underscore-in-googletest-name"); - CheckFactories.registerCheck( + CheckFactories.registerCheck( "google-readability-casting"); CheckFactories.registerCheck( "google-readability-todo"); diff --git a/clang-tools-extra/clang-tidy/llvm/UseNewMLIROpBuilderCheck.cpp b/clang-tools-extra/clang-tidy/llvm/UseNewMLIROpBuilderCheck.cpp index 55b383eca445..16f8786c4376 100644 --- a/clang-tools-extra/clang-tidy/llvm/UseNewMLIROpBuilderCheck.cpp +++ b/clang-tools-extra/clang-tidy/llvm/UseNewMLIROpBuilderCheck.cpp @@ -12,7 +12,6 @@ #include "clang/Lex/Lexer.h" #include "clang/Tooling/Transformer/RangeSelector.h" #include "clang/Tooling/Transformer/RewriteRule.h" -#include "clang/Tooling/Transformer/SourceCode.h" #include "clang/Tooling/Transformer/Stencil.h" #include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" diff --git a/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp b/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp index 411e16c87b1c..418b8ae40ea5 100644 --- a/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/ConfusableIdentifierCheck.cpp @@ -81,7 +81,8 @@ static llvm::SmallString<64U> skeleton(StringRef Name) { errs() << "Unicode conversion issue\n"; break; } - Skeleton.append((char *)BufferStart, (char *)IBuffer); + Skeleton.append(reinterpret_cast(BufferStart), + reinterpret_cast(IBuffer)); } } return Skeleton; diff --git a/clang-tools-extra/clang-tidy/misc/MisleadingBidirectionalCheck.cpp b/clang-tools-extra/clang-tidy/misc/MisleadingBidirectionalCheck.cpp index 4807567710f2..8a10f70c12f9 100644 --- a/clang-tools-extra/clang-tidy/misc/MisleadingBidirectionalCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/MisleadingBidirectionalCheck.cpp @@ -52,8 +52,9 @@ static bool containsMisleadingBidi(StringRef Buffer, } llvm::UTF32 CodePoint = 0; const llvm::ConversionResult Result = llvm::convertUTF8Sequence( - (const llvm::UTF8 **)&CurPtr, (const llvm::UTF8 *)Buffer.end(), - &CodePoint, llvm::strictConversion); + reinterpret_cast(&CurPtr), + reinterpret_cast(Buffer.end()), &CodePoint, + llvm::strictConversion); // If conversion fails, utf-8 is designed so that we can just try next char. if (Result != llvm::conversionOK) { diff --git a/clang-tools-extra/clang-tidy/misc/MisleadingIdentifierCheck.cpp b/clang-tools-extra/clang-tidy/misc/MisleadingIdentifierCheck.cpp index 335fffc5d47a..9c0de87f1419 100644 --- a/clang-tools-extra/clang-tidy/misc/MisleadingIdentifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/MisleadingIdentifierCheck.cpp @@ -125,7 +125,8 @@ static bool hasRTLCharacters(StringRef Buffer) { while (CurPtr < EndPtr) { llvm::UTF32 CodePoint = 0; const llvm::ConversionResult Result = llvm::convertUTF8Sequence( - (const llvm::UTF8 **)&CurPtr, (const llvm::UTF8 *)EndPtr, &CodePoint, + reinterpret_cast(&CurPtr), + reinterpret_cast(EndPtr), &CodePoint, llvm::strictConversion); if (Result != llvm::conversionOK) break; diff --git a/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp b/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp index a44e9b381d98..0471ba8ae291 100644 --- a/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/NewDeleteOverloadsCheck.cpp @@ -114,17 +114,15 @@ hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD, RD = MD->getParent(); } - for (const auto &BS : RD->bases()) { + return llvm::any_of(RD->bases(), [&](const CXXBaseSpecifier &BS) { // We can't say much about a dependent base class, but to avoid false // positives assume it can have a corresponding overload. if (BS.getType()->isDependentType()) return true; - if (const auto *BaseRD = BS.getType()->getAsCXXRecordDecl()) - if (hasCorrespondingOverloadInBaseClass(MD, BaseRD)) - return true; - } - - return false; + if (const CXXRecordDecl *BaseRD = BS.getType()->getAsCXXRecordDecl()) + return hasCorrespondingOverloadInBaseClass(MD, BaseRD); + return false; + }); } void NewDeleteOverloadsCheck::registerMatchers(MatchFinder *Finder) { diff --git a/clang-tools-extra/clang-tidy/misc/UnusedParametersCheck.cpp b/clang-tools-extra/clang-tidy/misc/UnusedParametersCheck.cpp index 870905087a9b..9c38bb129022 100644 --- a/clang-tools-extra/clang-tidy/misc/UnusedParametersCheck.cpp +++ b/clang-tools-extra/clang-tidy/misc/UnusedParametersCheck.cpp @@ -30,13 +30,10 @@ static bool isOverrideMethod(const FunctionDecl *Function) { static bool hasAttrAfterParam(const SourceManager *SourceManager, const ParmVarDecl *Param) { - for (const auto *Attr : Param->attrs()) { - if (SourceManager->isBeforeInTranslationUnit(Param->getLocation(), - Attr->getLocation())) { - return true; - } - } - return false; + return llvm::any_of(Param->attrs(), [&](const Attr *Attr) { + return SourceManager->isBeforeInTranslationUnit(Param->getLocation(), + Attr->getLocation()); + }); } void UnusedParametersCheck::registerMatchers(MatchFinder *Finder) { diff --git a/clang-tools-extra/clang-tidy/modernize/AvoidCStyleCastCheck.cpp b/clang-tools-extra/clang-tidy/modernize/AvoidCStyleCastCheck.cpp new file mode 100644 index 000000000000..7f9bf6397248 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/AvoidCStyleCastCheck.cpp @@ -0,0 +1,290 @@ +//===----------------------------------------------------------------------===// +// +// 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 "AvoidCStyleCastCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +void AvoidCStyleCastCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher( + cStyleCastExpr( + // Filter out (EnumType)IntegerLiteral construct, which is generated + // for non-type template arguments of enum types. + // FIXME: Remove this once this is fixed in the AST. + unless(hasParent(substNonTypeTemplateParmExpr()))) + .bind("cast"), + this); + + Finder->addMatcher( + cxxFunctionalCastExpr( + hasDestinationType(hasCanonicalType(anyOf( + builtinType(), references(qualType()), pointsTo(qualType())))), + unless( + hasSourceExpression(anyOf(cxxConstructExpr(), initListExpr())))) + .bind("cast"), + this); +} + +static bool needsConstCast(QualType SourceType, QualType DestType) { + while ((SourceType->isPointerType() && DestType->isPointerType()) || + (SourceType->isReferenceType() && DestType->isReferenceType())) { + SourceType = SourceType->getPointeeType(); + DestType = DestType->getPointeeType(); + if (SourceType.isConstQualified() && !DestType.isConstQualified()) { + return (SourceType->isPointerType() == DestType->isPointerType()) && + (SourceType->isReferenceType() == DestType->isReferenceType()); + } + } + return false; +} + +static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) { + while ((T1->isPointerType() && T2->isPointerType()) || + (T1->isReferenceType() && T2->isReferenceType())) { + T1 = T1->getPointeeType(); + T2 = T2->getPointeeType(); + } + return T1.getUnqualifiedType() == T2.getUnqualifiedType(); +} + +static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr) { + if (const auto *CastExpr = dyn_cast(Expr)) + return CharSourceRange::getCharRange( + CastExpr->getLParenLoc(), + CastExpr->getSubExprAsWritten()->getBeginLoc()); + if (const auto *CastExpr = dyn_cast(Expr)) + return CharSourceRange::getCharRange(CastExpr->getBeginLoc(), + CastExpr->getLParenLoc()); + llvm_unreachable("Unsupported CastExpr"); +} + +static StringRef getDestTypeString(const SourceManager &SM, + const LangOptions &LangOpts, + const ExplicitCastExpr *Expr) { + SourceLocation BeginLoc; + SourceLocation EndLoc; + + if (const auto *CastExpr = dyn_cast(Expr)) { + BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(1); + EndLoc = CastExpr->getRParenLoc().getLocWithOffset(-1); + } else if (const auto *CastExpr = dyn_cast(Expr)) { + BeginLoc = CastExpr->getBeginLoc(); + EndLoc = CastExpr->getLParenLoc().getLocWithOffset(-1); + } else + llvm_unreachable("Unsupported CastExpr"); + + return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + SM, LangOpts); +} + +static bool sameTypeAsWritten(QualType X, QualType Y) { + if (X.getCanonicalType() != Y.getCanonicalType()) + return false; + + auto TC = X->getTypeClass(); + if (TC != Y->getTypeClass()) + return false; + + switch (TC) { + case Type::Typedef: + return declaresSameEntity(cast(X)->getDecl(), + cast(Y)->getDecl()); + case Type::Pointer: + return sameTypeAsWritten(cast(X)->getPointeeType(), + cast(Y)->getPointeeType()); + case Type::RValueReference: + case Type::LValueReference: + return sameTypeAsWritten(cast(X)->getPointeeType(), + cast(Y)->getPointeeType()); + default: + return true; + } +} + +void AvoidCStyleCastCheck::check(const MatchFinder::MatchResult &Result) { + const auto *CastExpr = Result.Nodes.getNodeAs("cast"); + + // Ignore casts in macros. + if (CastExpr->getExprLoc().isMacroID()) + return; + + // Casting to void is an idiomatic way to mute "unused variable" and similar + // warnings. + if (CastExpr->getCastKind() == CK_ToVoid) + return; + + auto IsFunction = [](QualType T) { + T = T.getCanonicalType().getNonReferenceType(); + return T->isFunctionType() || T->isFunctionPointerType() || + T->isMemberFunctionPointerType(); + }; + + const QualType DestTypeAsWritten = + CastExpr->getTypeAsWritten().getUnqualifiedType(); + const QualType SourceTypeAsWritten = + CastExpr->getSubExprAsWritten()->getType().getUnqualifiedType(); + const QualType SourceType = SourceTypeAsWritten.getCanonicalType(); + const QualType DestType = DestTypeAsWritten.getCanonicalType(); + + CharSourceRange ReplaceRange = getReplaceRange(CastExpr); + + const bool FnToFnCast = + IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten); + + const bool ConstructorCast = !CastExpr->getTypeAsWritten().hasQualifiers() && + DestTypeAsWritten->isRecordType() && + !DestTypeAsWritten->isElaboratedTypeSpecifier(); + + if (CastExpr->getCastKind() == CK_NoOp && !FnToFnCast) { + // Function pointer/reference casts may be needed to resolve ambiguities in + // case of overloaded functions, so detection of redundant casts is trickier + // in this case. Don't emit "redundant cast" warnings for function + // pointer/reference types. + if (sameTypeAsWritten(SourceTypeAsWritten, DestTypeAsWritten)) { + diag(CastExpr->getBeginLoc(), "redundant cast to the same type") + << FixItHint::CreateRemoval(ReplaceRange); + return; + } + } + + // The rest of this check is only relevant to C++. + // We also disable it for Objective-C++. + if (!getLangOpts().CPlusPlus || getLangOpts().ObjC) + return; + // Ignore code inside extern "C" {} blocks. + if (!match(expr(hasAncestor(linkageSpecDecl())), *CastExpr, *Result.Context) + .empty()) + return; + // Ignore code in .c files and headers included from them, even if they are + // compiled as C++. + if (getCurrentMainFile().ends_with(".c")) + return; + + SourceManager &SM = *Result.SourceManager; + + // Ignore code in .c files #included in other files (which shouldn't be done, + // but people still do this for test and other purposes). + if (SM.getFilename(SM.getSpellingLoc(CastExpr->getBeginLoc())) + .ends_with(".c")) + return; + + // Leave type spelling exactly as it was (unlike + // getTypeAsWritten().getAsString() which would spell enum types 'enum X'). + StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr); + + auto Diag = + diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0"); + + auto ReplaceWithCast = [&](std::string CastText) { + const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts(); + if (!isa(SubExpr) && !isa(CastExpr)) { + CastText.push_back('('); + Diag << FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM, + getLangOpts()), + ")"); + } + Diag << FixItHint::CreateReplacement(ReplaceRange, CastText); + }; + auto ReplaceWithNamedCast = [&](StringRef CastType) { + Diag << CastType; + ReplaceWithCast((CastType + "<" + DestTypeString + ">").str()); + }; + auto ReplaceWithConstructorCall = [&]() { + Diag << "constructor call syntax"; + // FIXME: Validate DestTypeString, maybe. + ReplaceWithCast(DestTypeString.str()); + }; + // Suggest appropriate C++ cast. See [expr.cast] for cast notation semantics. + switch (CastExpr->getCastKind()) { + case CK_FunctionToPointerDecay: + ReplaceWithNamedCast("static_cast"); + return; + case CK_ConstructorConversion: + if (ConstructorCast) { + ReplaceWithConstructorCall(); + } else { + ReplaceWithNamedCast("static_cast"); + } + return; + case CK_NoOp: + if (FnToFnCast) { + ReplaceWithNamedCast("static_cast"); + return; + } + if (SourceType == DestType) { + Diag << "static_cast (if needed, the cast may be redundant)"; + ReplaceWithCast(("static_cast<" + DestTypeString + ">").str()); + return; + } + if (needsConstCast(SourceType, DestType) && + pointedUnqualifiedTypesAreEqual(SourceType, DestType)) { + ReplaceWithNamedCast("const_cast"); + return; + } + if (ConstructorCast) { + ReplaceWithConstructorCall(); + return; + } + if (DestType->isReferenceType()) { + const QualType Dest = DestType.getNonReferenceType(); + const QualType Source = SourceType.getNonReferenceType(); + if (Source == Dest.withConst() || + SourceType.getNonReferenceType() == DestType.getNonReferenceType()) { + ReplaceWithNamedCast("const_cast"); + return; + } + break; + } + if (DestType->isVoidPointerType() && SourceType->isPointerType() && + !SourceType->getPointeeType()->isPointerType()) { + ReplaceWithNamedCast("reinterpret_cast"); + return; + } + + [[fallthrough]]; + case clang::CK_IntegralCast: + // Convert integral and no-op casts between builtin types and enums to + // static_cast. A cast from enum to integer may be unnecessary, but it's + // still retained. + if ((SourceType->isBuiltinType() || SourceType->isEnumeralType()) && + (DestType->isBuiltinType() || DestType->isEnumeralType())) { + ReplaceWithNamedCast("static_cast"); + return; + } + break; + case CK_BitCast: + // FIXME: Suggest const_cast<...>(reinterpret_cast<...>(...)) replacement. + if (!needsConstCast(SourceType, DestType)) { + if (SourceType->isVoidPointerType()) + ReplaceWithNamedCast("static_cast"); + else + ReplaceWithNamedCast("reinterpret_cast"); + return; + } + break; + case CK_BaseToDerived: + if (!needsConstCast(SourceType, DestType)) { + ReplaceWithNamedCast("static_cast"); + return; + } + break; + default: + break; + } + + Diag << "static_cast/const_cast/reinterpret_cast"; +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/AvoidCStyleCastCheck.h b/clang-tools-extra/clang-tidy/modernize/AvoidCStyleCastCheck.h new file mode 100644 index 000000000000..123d524c6e3b --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/AvoidCStyleCastCheck.h @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// 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_MODERNIZE_AVOIDCSTYLECASTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOIDCSTYLECASTCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Finds usages of C-style casts. +/// +/// https://google.github.io/styleguide/cppguide.html#Casting +/// +/// Corresponding cpplint.py check name: 'readability/casting'. +/// +/// This check is similar to `-Wold-style-cast`, but it suggests automated fixes +/// in some cases. The reported locations should not be different from the +/// ones generated by `-Wold-style-cast`. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/avoid-c-style-cast.html +class AvoidCStyleCastCheck : public ClangTidyCheck { +public: + AvoidCStyleCastCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOIDCSTYLECASTCHECK_H diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index 882f2dc9fb4d..488c35966101 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangTidyModernizeModule STATIC AvoidBindCheck.cpp AvoidCArraysCheck.cpp + AvoidCStyleCastCheck.cpp AvoidSetjmpLongjmpCheck.cpp AvoidVariadicFunctionsCheck.cpp ConcatNestedNamespacesCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp index 668ba08400b2..19b406f05a74 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp @@ -510,27 +510,24 @@ static bool canBeModified(ASTContext *Context, const Expr *E) { /// Returns true when it can be guaranteed that the elements of the /// container are not being modified. static bool usagesAreConst(ASTContext *Context, const UsageResult &Usages) { - for (const Usage &U : Usages) { + return llvm::none_of(Usages, [&Context](const Usage &U) { // Lambda captures are just redeclarations (VarDecl) of the same variable, // not expressions. If we want to know if a variable that is captured by // reference can be modified in an usage inside the lambda's body, we need // to find the expression corresponding to that particular usage, later in // this loop. - if (U.Kind != Usage::UK_CaptureByCopy && U.Kind != Usage::UK_CaptureByRef && - canBeModified(Context, U.Expression)) - return false; - } - return true; + return U.Kind != Usage::UK_CaptureByCopy && + U.Kind != Usage::UK_CaptureByRef && + canBeModified(Context, U.Expression); + }); } /// Returns true if the elements of the container are never accessed /// by reference. static bool usagesReturnRValues(const UsageResult &Usages) { - for (const auto &U : Usages) { - if (U.Expression && !U.Expression->isPRValue()) - return false; - } - return true; + return llvm::all_of(Usages, [](const Usage &U) { + return !U.Expression || U.Expression->isPRValue(); + }); } /// Returns true if the container is const-qualified. diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp index 170a4f6d8731..f6685dda7e09 100644 --- a/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp +++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp @@ -89,13 +89,11 @@ bool DependencyFinderASTVisitor::VisitVarDecl(VarDecl *V) { // Next, check if the variable was removed from existence by an earlier // iteration. - for (const auto &I : *ReplacedVars) { - if (I.second == V) { - DependsOnInsideVariable = true; - return false; - } - } - return true; + if (llvm::none_of(*ReplacedVars, + [&](const auto &I) { return I.second == V; })) + return true; + DependsOnInsideVariable = true; + return false; } /// If we already created a variable for TheLoop, check to make sure @@ -234,11 +232,8 @@ static bool containsExpr(ASTContext *Context, const ContainerT *Container, const Expr *E) { llvm::FoldingSetNodeID ID; E->Profile(ID, *Context, true); - for (const auto &I : *Container) { - if (ID == I.second) - return true; - } - return false; + return llvm::any_of(*Container, + [&](const auto &I) { return ID == I.second; }); } /// Returns true when the index expression is a declaration reference to diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp index 7940939eb21a..39c5def0c826 100644 --- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// +#include "MakeSmartPtrCheck.h" #include "../utils/TypeTraits.h" -#include "MakeSharedCheck.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index 360e2b8434d0..a410f8da6af5 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -11,6 +11,7 @@ #include "../ClangTidyModuleRegistry.h" #include "AvoidBindCheck.h" #include "AvoidCArraysCheck.h" +#include "AvoidCStyleCastCheck.h" #include "AvoidSetjmpLongjmpCheck.h" #include "AvoidVariadicFunctionsCheck.h" #include "ConcatNestedNamespacesCheck.h" @@ -65,6 +66,8 @@ class ModernizeModule : public ClangTidyModule { void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { CheckFactories.registerCheck("modernize-avoid-bind"); CheckFactories.registerCheck("modernize-avoid-c-arrays"); + CheckFactories.registerCheck( + "modernize-avoid-c-style-cast"); CheckFactories.registerCheck( "modernize-avoid-setjmp-longjmp"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp index a257f5325f78..09d98ee8bea6 100644 --- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp @@ -196,11 +196,7 @@ static bool hasRValueOverload(const CXXConstructorDecl *Ctor, return true; }; - for (const auto *Candidate : Record->ctors()) { - if (IsRValueOverload(Candidate)) - return true; - } - return false; + return llvm::any_of(Record->ctors(), IsRValueOverload); } /// Find all references to \p ParamDecl across all of the diff --git a/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp index e585dd1d4000..ca97b11b9990 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseEmplaceCheck.cpp @@ -44,17 +44,12 @@ AST_MATCHER_P(NamedDecl, hasAnyNameIgnoringTemplates, std::vector, // clang/lib/ASTMatchers/ASTMatchersInternal.cpp and checks whether // FullNameTrimmed matches any of the given Names. const StringRef FullNameTrimmedRef = FullNameTrimmed; - for (const StringRef Pattern : Names) { - if (Pattern.starts_with("::")) { - if (FullNameTrimmed == Pattern) - return true; - } else if (FullNameTrimmedRef.ends_with(Pattern) && - FullNameTrimmedRef.drop_back(Pattern.size()).ends_with("::")) { - return true; - } - } - - return false; + return llvm::any_of(Names, [&](const StringRef Pattern) { + if (Pattern.starts_with("::")) + return FullNameTrimmed == Pattern; + return FullNameTrimmedRef.ends_with(Pattern) && + FullNameTrimmedRef.drop_back(Pattern.size()).ends_with("::"); + }); } // Checks if the given matcher is the last argument of the given CallExpr. diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h index 18cff9aa962b..7d771c4446cd 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h +++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h @@ -36,7 +36,7 @@ class UseStdPrintCheck : public ClangTidyCheck { } private: - Preprocessor *PP; + Preprocessor *PP = nullptr; bool StrictMode; std::vector PrintfLikeFunctions; std::vector FprintfLikeFunctions; diff --git a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp index f9afd5044b58..02865b65a9ec 100644 --- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp +++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp @@ -55,13 +55,12 @@ struct UnqualNameVisitor : public RecursiveASTVisitor { bool visitUnqualName(StringRef UnqualName) { // Check for collisions with function arguments. - for (const ParmVarDecl *Param : F.parameters()) + Collision = llvm::any_of(F.parameters(), [&](const ParmVarDecl *Param) { if (const IdentifierInfo *Ident = Param->getIdentifier()) - if (Ident->getName() == UnqualName) { - Collision = true; - return true; - } - return false; + return Ident->getName() == UnqualName; + return false; + }); + return Collision; } bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) { diff --git a/clang-tools-extra/clang-tidy/objc/MissingHashCheck.cpp b/clang-tools-extra/clang-tidy/objc/MissingHashCheck.cpp index 7b48fd9f77bc..b8010e0d29eb 100644 --- a/clang-tools-extra/clang-tidy/objc/MissingHashCheck.cpp +++ b/clang-tools-extra/clang-tidy/objc/MissingHashCheck.cpp @@ -25,11 +25,9 @@ AST_MATCHER_P(ObjCImplementationDecl, hasInterface, AST_MATCHER_P(ObjCContainerDecl, hasInstanceMethod, ast_matchers::internal::Matcher, Base) { // Check each instance method against the provided matcher. - for (const auto *I : Node.instance_methods()) { - if (Base.matches(*I, Finder, Builder)) - return true; - } - return false; + return llvm::any_of(Node.instance_methods(), [&](const ObjCMethodDecl *I) { + return Base.matches(*I, Finder, Builder); + }); } } // namespace diff --git a/clang-tools-extra/clang-tidy/openmp/UseDefaultNoneCheck.cpp b/clang-tools-extra/clang-tidy/openmp/UseDefaultNoneCheck.cpp index d02ab728547a..5bd842be67ef 100644 --- a/clang-tools-extra/clang-tidy/openmp/UseDefaultNoneCheck.cpp +++ b/clang-tools-extra/clang-tidy/openmp/UseDefaultNoneCheck.cpp @@ -37,8 +37,9 @@ void UseDefaultNoneCheck::check(const MatchFinder::MatchResult &Result) { "OpenMP directive '%0' specifies 'default(%1)' clause, consider using " "'default(none)' clause instead") << getOpenMPDirectiveName(Directive->getDirectiveKind()) - << getOpenMPSimpleClauseTypeName(Clause->getClauseKind(), - unsigned(Clause->getDefaultKind())); + << getOpenMPSimpleClauseTypeName( + Clause->getClauseKind(), + llvm::to_underlying(Clause->getDefaultKind())); diag(Clause->getBeginLoc(), "existing 'default' clause specified here", DiagnosticIDs::Note); return; diff --git a/clang-tools-extra/clang-tidy/performance/TriviallyDestructibleCheck.cpp b/clang-tools-extra/clang-tidy/performance/TriviallyDestructibleCheck.cpp index 2f54b17367b0..416c41d7acd6 100644 --- a/clang-tools-extra/clang-tidy/performance/TriviallyDestructibleCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/TriviallyDestructibleCheck.cpp @@ -23,12 +23,9 @@ namespace { AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); } AST_MATCHER_P(CXXRecordDecl, hasBase, Matcher, InnerMatcher) { - for (const CXXBaseSpecifier &BaseSpec : Node.bases()) { - const QualType BaseType = BaseSpec.getType(); - if (InnerMatcher.matches(BaseType, Finder, Builder)) - return true; - } - return false; + return llvm::any_of(Node.bases(), [&](const CXXBaseSpecifier &BaseSpec) { + return InnerMatcher.matches(BaseSpec.getType(), Finder, Builder); + }); } } // namespace diff --git a/clang-tools-extra/clang-tidy/readability/AmbiguousSmartptrResetCallCheck.cpp b/clang-tools-extra/clang-tidy/readability/AmbiguousSmartptrResetCallCheck.cpp index 22ff5ce1545a..ef9263beebfd 100644 --- a/clang-tools-extra/clang-tidy/readability/AmbiguousSmartptrResetCallCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/AmbiguousSmartptrResetCallCheck.cpp @@ -20,12 +20,9 @@ namespace clang::tidy::readability { namespace { AST_MATCHER(CXXMethodDecl, hasOnlyDefaultParameters) { - for (const auto *Param : Node.parameters()) { - if (!Param->hasDefaultArg()) - return false; - } - - return true; + return llvm::all_of(Node.parameters(), [](const ParmVarDecl *Param) { + return Param->hasDefaultArg(); + }); } const auto DefaultSmartPointers = "::std::shared_ptr;::std::unique_ptr;" diff --git a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp index b42d8a750290..a966f1f6e24c 100644 --- a/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp @@ -557,7 +557,7 @@ void FunctionCognitiveComplexityCheck::check( // Increase, on the other hand, can be 0. diag(Detail.Loc, Msgs[MsgId], DiagnosticIDs::Note) - << (unsigned)Increase << (unsigned)Detail.Nesting << 1 + Detail.Nesting; + << Increase << Detail.Nesting << 1 + Detail.Nesting; } } diff --git a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp index d1583a62a8e5..79f8437057b2 100644 --- a/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -318,8 +318,8 @@ std::string IdentifierNamingCheck::HungarianNotation::getDeclTypeName( if (!EOL) EOL = Begin + strlen(Begin); - const char *PosList[] = {strchr(Begin, '='), strchr(Begin, ';'), - strchr(Begin, ','), strchr(Begin, ')'), EOL}; + const char *const PosList[] = {strchr(Begin, '='), strchr(Begin, ';'), + strchr(Begin, ','), strchr(Begin, ')'), EOL}; for (const auto &Pos : PosList) { if (Pos > Begin) EOL = std::min(EOL, Pos); diff --git a/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp index c49684112a5d..7b27ab92923a 100644 --- a/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -107,6 +107,10 @@ findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, while (SourceParamIt != ParameterSourceDeclaration->param_end() && OtherParamIt != OtherDeclaration->param_end()) { + if ((*SourceParamIt)->isParameterPack() != + (*OtherParamIt)->isParameterPack()) + break; + auto SourceParamName = (*SourceParamIt)->getName(); auto OtherParamName = (*OtherParamIt)->getName(); diff --git a/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp index 269996dc0791..05f31c76c4c7 100644 --- a/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp @@ -136,11 +136,9 @@ getRepresentation(const std::vector &Config, template static bool isAnyOperatorEnabled(const std::vector &Config, const T &Operators) { - for (const auto &[traditional, alternative] : Operators) { - if (!getRepresentation(Config, traditional, alternative).empty()) - return true; - } - return false; + return llvm::any_of(Operators, [&](const auto &Op) { + return !getRepresentation(Config, Op.first, Op.second).empty(); + }); } OperatorsRepresentationCheck::OperatorsRepresentationCheck( diff --git a/clang-tools-extra/clang-tidy/readability/RedundantCastingCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantCastingCheck.cpp index d11c41c33d2b..21f481a71821 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantCastingCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantCastingCheck.cpp @@ -9,6 +9,7 @@ #include "RedundantCastingCheck.h" #include "../utils/FixItHintUtils.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/TypeBase.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" @@ -29,7 +30,7 @@ static bool areTypesEqual(QualType S, QualType D) { const QualType PtrD = D->getPointeeType(); if (!PtrS.isNull() && !PtrD.isNull()) - return areTypesEqual(PtrS, PtrD); + return areTypesEqual(PtrS.IgnoreParens(), PtrD.IgnoreParens()); const DeducedType *DT = S->getContainedDeducedType(); if (DT && DT->isDeduced()) diff --git a/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp index 132b7ddc4311..46cbaa1f7030 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.cpp @@ -8,84 +8,58 @@ #include "RedundantControlFlowCheck.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchersMacros.h" #include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; namespace clang::tidy::readability { -static const char *const RedundantReturnDiag = +namespace { + +AST_MATCHER_P(CompoundStmt, hasFinalStmt, StatementMatcher, InnerMatcher) { + return !Node.body_empty() && + InnerMatcher.matches(*Node.body_back(), Finder, Builder); +} + +} // namespace + +static constexpr StringRef RedundantReturnDiag = "redundant return statement at the end " "of a function with a void return type"; -static const char *const RedundantContinueDiag = +static constexpr StringRef RedundantContinueDiag = "redundant continue statement at the " "end of loop statement"; -static bool isLocationInMacroExpansion(const SourceManager &SM, - SourceLocation Loc) { - return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); -} - void RedundantControlFlowCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( - functionDecl(isDefinition(), returns(voidType()), - hasBody(compoundStmt(hasAnySubstatement( - returnStmt(unless(has(expr()))))) - .bind("return"))), - this); - Finder->addMatcher( - mapAnyOf(forStmt, cxxForRangeStmt, whileStmt, doStmt) - .with(hasBody(compoundStmt(hasAnySubstatement(continueStmt())) - .bind("continue"))), + functionDecl(returns(voidType()), + hasBody(compoundStmt(hasFinalStmt( + returnStmt(unless(has(expr()))).bind("stmt"))))), this); + Finder->addMatcher(mapAnyOf(forStmt, cxxForRangeStmt, whileStmt, doStmt) + .with(hasBody(compoundStmt( + hasFinalStmt(continueStmt().bind("stmt"))))), + this); } void RedundantControlFlowCheck::check(const MatchFinder::MatchResult &Result) { - if (const auto *Return = Result.Nodes.getNodeAs("return")) - checkRedundantReturn(Result, Return); - else if (const auto *Continue = - Result.Nodes.getNodeAs("continue")) - checkRedundantContinue(Result, Continue); -} - -void RedundantControlFlowCheck::checkRedundantReturn( - const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { - const CompoundStmt::const_reverse_body_iterator Last = Block->body_rbegin(); - if (const auto *Return = dyn_cast(*Last)) - issueDiagnostic(Result, Block, Return->getSourceRange(), - RedundantReturnDiag); -} - -void RedundantControlFlowCheck::checkRedundantContinue( - const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { - const CompoundStmt::const_reverse_body_iterator Last = Block->body_rbegin(); - if (const auto *Continue = dyn_cast(*Last)) - issueDiagnostic(Result, Block, Continue->getSourceRange(), - RedundantContinueDiag); -} + const auto &RedundantStmt = *Result.Nodes.getNodeAs("stmt"); + const SourceRange StmtRange = RedundantStmt.getSourceRange(); -void RedundantControlFlowCheck::issueDiagnostic( - const MatchFinder::MatchResult &Result, const CompoundStmt *const Block, - const SourceRange &StmtRange, const char *const Diag) { - const SourceManager &SM = *Result.SourceManager; - if (isLocationInMacroExpansion(SM, StmtRange.getBegin())) + if (StmtRange.getBegin().isMacroID()) return; - const CompoundStmt::const_reverse_body_iterator Previous = - ++Block->body_rbegin(); - SourceLocation Start; - if (Previous != Block->body_rend()) - Start = Lexer::findLocationAfterToken( - cast(*Previous)->getEndLoc(), tok::semi, SM, getLangOpts(), - /*SkipTrailingWhitespaceAndNewLine=*/true); - if (!Start.isValid()) - Start = StmtRange.getBegin(); - auto RemovedRange = CharSourceRange::getCharRange( - Start, Lexer::findLocationAfterToken( - StmtRange.getEnd(), tok::semi, SM, getLangOpts(), - /*SkipTrailingWhitespaceAndNewLine=*/true)); + const auto RemovedRange = CharSourceRange::getCharRange( + StmtRange.getBegin(), + Lexer::findLocationAfterToken(StmtRange.getEnd(), tok::semi, + *Result.SourceManager, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); - diag(StmtRange.getBegin(), Diag) << FixItHint::CreateRemoval(RemovedRange); + diag(StmtRange.getBegin(), isa(RedundantStmt) + ? RedundantReturnDiag + : RedundantContinueDiag) + << FixItHint::CreateRemoval(RemovedRange); } } // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.h index fde305039d4c..028f9948f74a 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.h +++ b/clang-tools-extra/clang-tidy/readability/RedundantControlFlowCheck.h @@ -30,19 +30,6 @@ class RedundantControlFlowCheck : public ClangTidyCheck { std::optional getCheckTraversalKind() const override { return TK_IgnoreUnlessSpelledInSource; } - -private: - void - checkRedundantReturn(const ast_matchers::MatchFinder::MatchResult &Result, - const CompoundStmt *Block); - - void - checkRedundantContinue(const ast_matchers::MatchFinder::MatchResult &Result, - const CompoundStmt *Block); - - void issueDiagnostic(const ast_matchers::MatchFinder::MatchResult &Result, - const CompoundStmt *Block, const SourceRange &StmtRange, - const char *Diag); }; } // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp index 76adaa80207d..7b6eb3443d27 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantInlineSpecifierCheck.cpp @@ -16,10 +16,9 @@ #include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" #include "clang/Lex/Token.h" -#include "../utils/LexerUtils.h" - using namespace clang::ast_matchers; namespace clang::tidy::readability { diff --git a/clang-tools-extra/clang-tidy/readability/RedundantStringInitCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantStringInitCheck.cpp index 756fe437b3e1..5cbbbcad3bf5 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantStringInitCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantStringInitCheck.cpp @@ -7,13 +7,11 @@ //===----------------------------------------------------------------------===// #include "RedundantStringInitCheck.h" -#include "../utils/Matchers.h" #include "../utils/OptionsUtils.h" #include "clang/ASTMatchers/ASTMatchers.h" #include using namespace clang::ast_matchers; -using namespace clang::tidy::matchers; namespace clang::tidy::readability { diff --git a/clang-tools-extra/clang-tidy/readability/RedundantTypenameCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantTypenameCheck.cpp index a4edd2b46b86..5f2519ce9d5c 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantTypenameCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantTypenameCheck.cpp @@ -47,6 +47,9 @@ void RedundantTypenameCheck::check(const MatchFinder::MatchResult &Result) { const SourceLocation ElaboratedKeywordLoc = [&] { if (const auto *NonDependentTypeLoc = Result.Nodes.getNodeAs("nonDependentTypeLoc")) { + if (NonDependentTypeLoc->getType()->isDependentType()) + return SourceLocation(); + if (const auto TL = NonDependentTypeLoc->getAs()) return TL.getElaboratedKeywordLoc(); @@ -59,8 +62,7 @@ void RedundantTypenameCheck::check(const MatchFinder::MatchResult &Result) { if (const auto TL = NonDependentTypeLoc->getAs()) - if (!TL.getType()->isDependentType()) - return TL.getElaboratedKeywordLoc(); + return TL.getElaboratedKeywordLoc(); } else { TypeLoc InnermostTypeLoc = *Result.Nodes.getNodeAs("dependentTypeLoc"); diff --git a/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp index d9ccd9920bb6..0b52a9664a7a 100644 --- a/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp @@ -758,7 +758,7 @@ bool SuspiciousCallArgumentCheck::areParamAndArgComparable( bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1, std::size_t Position2) const { - for (const Heuristic H : AppliedHeuristics) { + return llvm::any_of(AppliedHeuristics, [&](Heuristic H) { const bool A1ToP2Similar = areNamesSimilar( ArgNames[Position2], ParamNames[Position1], H, BoundKind::SimilarAbove); const bool A2ToP1Similar = areNamesSimilar( @@ -771,11 +771,9 @@ bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1, !areNamesSimilar(ArgNames[Position2], ParamNames[Position2], H, BoundKind::DissimilarBelow); - if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar && - A2ToP2Dissimilar) - return true; - } - return false; + return (A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar && + A2ToP2Dissimilar; + }); } bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg, diff --git a/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp b/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp index 5a7add88d6ee..ee0810b45559 100644 --- a/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/UseStdMinMaxCheck.cpp @@ -96,12 +96,11 @@ static QualType getReplacementCastType(const Expr *CondLhs, const Expr *CondRhs, return GlobalImplicitCastType; } -static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, - const Expr *AssignLhs, - const SourceManager &Source, - const LangOptions &LO, - StringRef FunctionName, - const BinaryOperator *BO) { +static std::string +createReplacement(const Expr *CondLhs, const Expr *CondRhs, + const Expr *AssignLhs, const SourceManager &Source, + const LangOptions &LO, StringRef FunctionName, + const BinaryOperator *BO, StringRef Comment = "") { const llvm::StringRef CondLhsStr = Lexer::getSourceText( Source.getExpansionRange(CondLhs->getSourceRange()), Source, LO); const llvm::StringRef CondRhsStr = Lexer::getSourceText( @@ -116,7 +115,8 @@ static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, (!GlobalImplicitCastType.isNull() ? "<" + GlobalImplicitCastType.getAsString() + ">(" : "(") + - CondLhsStr + ", " + CondRhsStr + ");") + CondLhsStr + ", " + CondRhsStr + ");" + (Comment.empty() ? "" : " ") + + Comment) .str(); } @@ -172,13 +172,65 @@ void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) { auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) { const SourceManager &Source = *Result.SourceManager; + llvm::SmallString<64> Comment; + + const auto AppendNormalized = [&](llvm::StringRef Text) { + Text = Text.ltrim(); + if (!Text.empty()) { + if (!Comment.empty()) + Comment += " "; + Comment += Text; + } + }; + + const auto GetSourceText = [&](SourceLocation StartLoc, + SourceLocation EndLoc) { + return Lexer::getSourceText( + CharSourceRange::getCharRange( + Lexer::getLocForEndOfToken(StartLoc, 0, Source, LO), EndLoc), + Source, LO); + }; + + // Captures: + // if (cond) // Comment A + // if (cond) /* Comment A */ { ... } + // if (cond) /* Comment A */ x = y; + AppendNormalized( + GetSourceText(If->getRParenLoc(), If->getThen()->getBeginLoc())); + + if (const auto *CS = dyn_cast(If->getThen())) { + const Stmt *Inner = CS->body_front(); + + // Captures: + // if (cond) { // Comment B + // ... + // } + // if (cond) { /* Comment B */ x = y; } + AppendNormalized(GetSourceText(CS->getBeginLoc(), Inner->getBeginLoc())); + + // Captures: + // if (cond) { x = y; // Comment C } + // if (cond) { x = y; /* Comment C */ } + llvm::StringRef PostInner = + GetSourceText(Inner->getEndLoc(), CS->getEndLoc()); + + // Strip the trailing semicolon to avoid fixes like: + // x = std::min(x, y);; // comment + const size_t Semi = PostInner.find(';'); + if (Semi != llvm::StringRef::npos && + PostInner.take_front(Semi).trim().empty()) { + PostInner = PostInner.drop_front(Semi + 1); + } + AppendNormalized(PostInner); + } + diag(IfLocation, "use `%0` instead of `%1`") << FunctionName << BinaryOp->getOpcodeStr() << FixItHint::CreateReplacement( SourceRange(IfLocation, Lexer::getLocForEndOfToken( ThenLocation, 0, Source, LO)), createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO, - FunctionName, BinaryOp)) + FunctionName, BinaryOp, Comment)) << IncludeInserter.createIncludeInsertion( Source.getFileID(If->getBeginLoc()), AlgorithmHeader); }; diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp index bc6bd164e24f..6a1f61dd6b9e 100644 --- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp +++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp @@ -104,8 +104,7 @@ Configuration files: )"); const char DefaultChecks[] = // Enable these checks by default: - "clang-diagnostic-*," // * compiler diagnostics - "clang-analyzer-*"; // * Static Analyzer checks + "clang-diagnostic-*"; // * compiler diagnostics static cl::opt Checks("checks", desc(R"( Comma-separated list of globs with optional '-' diff --git a/clang-tools-extra/clang-tidy/tool/check_alphabetical_order.py b/clang-tools-extra/clang-tidy/tool/check_alphabetical_order.py new file mode 100644 index 000000000000..66819aba435e --- /dev/null +++ b/clang-tools-extra/clang-tidy/tool/check_alphabetical_order.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python3 +# +# ===-----------------------------------------------------------------------===# +# +# 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 +# +# ===-----------------------------------------------------------------------===# + +""" + +Clang-Tidy Alphabetical Order Checker +===================================== + +Normalize Clang-Tidy documentation with deterministic sorting for linting/tests. + +Behavior: +- Sort entries in docs/clang-tidy/checks/list.rst csv-table. +- Sort key sections in docs/ReleaseNotes.rst. +- Detect duplicated entries in 'Changes in existing checks'. + +Flags: + -o/--output Write normalized content to this path instead of updating docs. +""" + +import argparse +from collections import defaultdict +import io +from operator import itemgetter +import os +import re +import sys +from typing import ( + DefaultDict, + Final, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Tuple, +) + +# Matches a :doc:`label ` or :doc:`label` reference anywhere in text and +# captures the label. Used to sort bullet items alphabetically in ReleaseNotes +# items by their label. +DOC_LABEL_RN_RE: Final = re.compile(r":doc:`(?P -// HTML-INDEX: -// HTML-INDEX: -// HTML-INDEX:

    Comment 1

    -// HTML-INDEX: -// HTML-INDEX: -// HTML-INDEX:

    Comment 2

    -// HTML-INDEX: -// HTML-INDEX: -// HTML-INDEX:

    Comment 3

    +// HTML-INDEX:
    +// HTML-INDEX:
    enum Color
    +// HTML-INDEX:
    +// HTML-INDEX:
    NameValue
    NameValueComment
    {{Name}}{{ValueExpr}}{{>Comments}}{{>Comments}}
    enum ColorRed0Green1Blue2
    +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
    NameValue
    Red0
    Green1
    Blue2
    /** * @brief Shape Types */ enum class Shapes { // MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* - // HTML-INDEX-LINE:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

    + // HTML-INDEX-LINE-NOT:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

    /// Comment 1 Circle, @@ -65,17 +80,32 @@ enum class Shapes { // MD-INDEX: | Triangle | // MD-INDEX: **brief** Shape Types -// HTML-INDEX: enum class Shapes -// HTML-INDEX: Circle -// HTML-INDEX: 0 -// HTML-INDEX:

    Comment 1

    -// HTML-INDEX: Rectangle -// HTML-INDEX: 1 -// HTML-INDEX:

    Comment 2

    -// HTML-INDEX: Triangle -// HTML-INDEX: 2 -// HTML-INDEX:

    Comment 3

    - +// COM: FIXME: Serialize "enum class" in template +// HTML-INDEX:
    +// HTML-INDEX:
    enum Shapes
    +// HTML-INDEX:
    +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
    NameValue
    Circle0
    Rectangle1
    Triangle2
    + +// COM: FIXME: Add enums declared inside of classes to class template class Animals { // MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* // HTML-ANIMAL-LINE:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

    @@ -85,25 +115,25 @@ class Animals { */ enum AnimalType { // MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* - // HTML-ANIMAL-LINE:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

    + // HTML-ANIMAL-LINE-NOT:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

    Dog, ///< Man's best friend Cat, ///< Man's other best friend Iguana ///< A lizard }; }; -// HTML-ANIMAL:

    class Animals

    -// HTML-ANIMAL:

    Enums

    -// HTML-ANIMAL: enum AnimalType -// HTML-ANIMAL: Dog -// HTML-ANIMAL: 0 -// HTML-ANIMAL:

    Man's best friend

    -// HTML-ANIMAL: Cat -// HTML-ANIMAL: 1 -// HTML-ANIMAL:

    Man's other best friend

    -// HTML-ANIMAL: Iguana -// HTML-ANIMAL: 2 -// HTML-ANIMAL:

    A lizard

    +// HTML-ANIMAL-NOT:

    class Animals

    +// HTML-ANIMAL-NOT:

    Enums

    +// HTML-ANIMAL-NOT: enum AnimalType +// HTML-ANIMAL-NOT: Dog +// HTML-ANIMAL-NOT: 0 +// HTML-ANIMAL-NOT:

    Man's best friend

    +// HTML-ANIMAL-NOT: Cat +// HTML-ANIMAL-NOT: 1 +// HTML-ANIMAL-NOT:

    Man's other best friend

    +// HTML-ANIMAL-NOT: Iguana +// HTML-ANIMAL-NOT: 2 +// HTML-ANIMAL-NOT:

    A lizard

    // MD-ANIMAL: # class Animals // MD-ANIMAL: ## Enums @@ -120,7 +150,7 @@ namespace Vehicles { */ enum Car { // MD-VEHICLES-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]* - // HTML-VEHICLES-LINE:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp

    + // HTML-VEHICLES-LINE: Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp Sedan, ///< Comment 1 SUV, ///< Comment 2 @@ -139,20 +169,33 @@ enum Car { // MD-VEHICLES: | Hatchback | // MD-VEHICLES: **brief** specify type of car -// HTML-VEHICLES:

    namespace Vehicles

    -// HTML-VEHICLES: enum Car -// HTML-VEHICLES: Sedan -// HTML-VEHICLES: 0 -// HTML-VEHICLES:

    Comment 1

    -// HTML-VEHICLES: SUV -// HTML-VEHICLES: 1 -// HTML-VEHICLES:

    Comment 2

    -// HTML-VEHICLES: Pickup -// HTML-VEHICLES: 2 -// HTML-VEHICLES:

    Comment 3

    -// HTML-VEHICLES: Hatchback -// HTML-VEHICLES: 3 -// HTML-VEHICLES:

    Comment 4

    +// HTML-VEHICLES:
    +// HTML-VEHICLES:
    enum Car
    +// HTML-VEHICLES:
    +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES: +// HTML-VEHICLES:
    NameValue
    Sedan0
    SUV1
    Pickup2
    Hatchback3
    enum ColorUserSpecified { RedUserSpecified = 'A', @@ -166,10 +209,26 @@ enum ColorUserSpecified { // MD-INDEX: | GreenUserSpecified | // MD-INDEX: | BlueUserSpecified | -// HTML-INDEX: enum ColorUserSpecified -// HTML-INDEX: RedUserSpecified -// HTML-INDEX: 'A' -// HTML-INDEX: GreenUserSpecified -// HTML-INDEX: 2 -// HTML-INDEX: BlueUserSpecified -// HTML-INDEX: 'C' +// HTML-INDEX:
    +// HTML-INDEX:
    enum ColorUserSpecified
    +// HTML-INDEX:
    +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
    NameValue
    RedUserSpecified'A'
    GreenUserSpecified2
    BlueUserSpecified'C'
    diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp index 91160585bef1..d57e8a990c3f 100644 --- a/clang-tools-extra/test/clang-doc/json/class.cpp +++ b/clang-tools-extra/test/clang-doc/json/class.cpp @@ -30,6 +30,8 @@ struct MyClass { int protectedMethod(); int ProtectedField; +private: + int PrivateField; }; // CHECK: { @@ -47,9 +49,6 @@ struct MyClass { // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "TextComment": " It has some nice methods and fields." -// CHECK-NEXT: }, -// CHECK-NEXT: { -// CHECK-NEXT: "TextComment": "" // CHECK-NEXT: } // CHECK: "DocumentationFileName": "_ZTV7MyClass", // CHECK: "Enums": [ @@ -125,6 +124,7 @@ struct MyClass { // CHECK-NEXT: } // CHECK-NEXT: ], // CHECK-NEXT: "HasEnums": true, +// CHECK-NEXT: "HasPrivateMembers": true, // CHECK-NEXT: "HasPublicFunctions": true, // CHECK-NEXT: "HasPublicMembers": true, // CHECK-NEXT: "HasRecords": true, @@ -140,6 +140,13 @@ struct MyClass { // CHECK-NEXT: "GlobalNamespace" // CHECK-NEXT: ], // CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "PrivateMembers": [ +// CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, +// CHECK-NEXT: "Name": "PrivateField", +// CHECK-NEXT: "Type": "int" +// CHECK-NEXT: } +// CHECK-NEXT: ], // CHECK-NEXT: "ProtectedFunctions": [ // CHECK-NEXT: { // CHECK-NEXT: "InfoType": "function", @@ -161,6 +168,7 @@ struct MyClass { // CHECK-NEXT: ], // CHECK-NEXT: "ProtectedMembers": [ // CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, // CHECK-NEXT: "Name": "ProtectedField", // CHECK-NEXT: "Type": "int" // CHECK-NEXT: } @@ -201,6 +209,7 @@ struct MyClass { // CHECK-NEXT: }, // CHECK: "PublicMembers": [ // CHECK-NEXT: { +// CHECK-NEXT: "IsStatic": false, // CHECK-NEXT: "Name": "PublicField", // CHECK-NEXT: "Type": "int" // CHECK-NEXT: } diff --git a/clang-tools-extra/test/clang-doc/json/concept.cpp b/clang-tools-extra/test/clang-doc/json/concept.cpp index 5d8c47eff0a1..f4c4ad3946d4 100644 --- a/clang-tools-extra/test/clang-doc/json/concept.cpp +++ b/clang-tools-extra/test/clang-doc/json/concept.cpp @@ -31,6 +31,6 @@ concept Incrementable = requires(T x) { // CHECK-NEXT: "USR": "{{[0-9A-F]*}}" // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK: "Name": "", +// CHECK: "Name": "Global Namespace", // CHECK: "USR": "0000000000000000000000000000000000000000" // CHECK: } diff --git a/clang-tools-extra/test/clang-doc/json/inheritance.cpp b/clang-tools-extra/test/clang-doc/json/inheritance.cpp new file mode 100644 index 000000000000..53476da870c6 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/inheritance.cpp @@ -0,0 +1,111 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-doc --output=%t --format=json --executor=standalone %s +// RUN: FileCheck %s < %t/json/GlobalNamespace/_ZTV7MyClass.json + +class Virtual {}; +class Foo : virtual Virtual {}; +class Bar : Foo {}; +class Fizz : virtual Virtual {}; +class Buzz : Fizz {}; + +class MyClass : Bar, Buzz {}; + +// CHECK: "Bases": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Access": "private", +// CHECK-NEXT: "InfoType": "record", +// CHECK-NEXT: "IsParent": true, +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "IsVirtual": false, +// CHECK-NEXT: "MangledName": "", +// CHECK-NEXT: "Name": "Bar", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Access": "private", +// CHECK-NEXT: "InfoType": "record", +// CHECK-NEXT: "IsParent": false, +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "IsVirtual": false, +// CHECK-NEXT: "MangledName": "", +// CHECK-NEXT: "Name": "Foo", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Access": "private", +// CHECK-NEXT: "InfoType": "record", +// CHECK-NEXT: "IsParent": false, +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "IsVirtual": true, +// CHECK-NEXT: "MangledName": "", +// CHECK-NEXT: "Name": "Virtual", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Access": "private", +// CHECK-NEXT: "InfoType": "record", +// CHECK-NEXT: "IsParent": true, +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "IsVirtual": false, +// CHECK-NEXT: "MangledName": "", +// CHECK-NEXT: "Name": "Buzz", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Access": "private", +// CHECK-NEXT: "InfoType": "record", +// CHECK-NEXT: "IsParent": false, +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "IsVirtual": false, +// CHECK-NEXT: "MangledName": "", +// CHECK-NEXT: "Name": "Fizz", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "Access": "private", +// CHECK-NEXT: "End": true, +// CHECK-NEXT: "InfoType": "record", +// CHECK-NEXT: "IsParent": false, +// CHECK-NEXT: "IsTypedef": false, +// CHECK-NEXT: "IsVirtual": true, +// CHECK-NEXT: "MangledName": "", +// CHECK-NEXT: "Name": "Virtual", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "TagType": "struct", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK: "Parents": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "Bar", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "QualName": "Bar", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "End": true, +// CHECK-NEXT: "Name": "Buzz", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "QualName": "Buzz", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK: "VirtualParents": [ +// CHECK-NEXT: { +// CHECK-NEXT: "End": true, +// CHECK-NEXT: "Name": "Virtual", +// CHECK-NEXT: "Path": "GlobalNamespace", +// CHECK-NEXT: "QualName": "Virtual", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: } +// CHECK-NEXT: ] \ No newline at end of file diff --git a/clang-tools-extra/test/clang-doc/json/namespace.cpp b/clang-tools-extra/test/clang-doc/json/namespace.cpp index dd7a9af9c82a..c1370d9fe379 100644 --- a/clang-tools-extra/test/clang-doc/json/namespace.cpp +++ b/clang-tools-extra/test/clang-doc/json/namespace.cpp @@ -75,7 +75,7 @@ typedef int MyTypedef; // CHECK-NEXT: "HasEnums": true, // CHECK-NEXT: "HasRecords": true, // CHECK-NEXT: "InfoType": "namespace", -// CHECK-NEXT: "Name": "", +// CHECK-NEXT: "Name": "Global Namespace", // CHECK-NEXT: "Namespaces": [ // CHECK-NEXT: { // CHECK-NEXT: "End": true, diff --git a/clang-tools-extra/test/clang-doc/long-name.cpp b/clang-tools-extra/test/clang-doc/long-name.cpp index 77e50b1553ad..e4a5e29f973d 100644 --- a/clang-tools-extra/test/clang-doc/long-name.cpp +++ b/clang-tools-extra/test/clang-doc/long-name.cpp @@ -1,7 +1,7 @@ // FIXME: This test seems to break on windows, so disable it for now. // UNSUPPORTED: system-windows // RUN: rm -rf %t && mkdir -p %t -// RUN: clang-doc --output=%t --format=mustache --executor=standalone %s +// RUN: clang-doc --output=%t --format=html --executor=standalone %s // RUN: ls %t/json/GlobalNamespace | FileCheck %s -check-prefix=CHECK-JSON // RUN: ls %t/html/GlobalNamespace | FileCheck %s -check-prefix=CHECK-HTML diff --git a/clang-tools-extra/test/clang-doc/mustache-index.cpp b/clang-tools-extra/test/clang-doc/mustache-index.cpp index 19e0b32d3d4a..0aa6e21c37ca 100644 --- a/clang-tools-extra/test/clang-doc/mustache-index.cpp +++ b/clang-tools-extra/test/clang-doc/mustache-index.cpp @@ -1,5 +1,5 @@ // RUN: rm -rf %t && mkdir -p %t -// RUN: clang-doc --format=mustache --output=%t --executor=standalone %s +// RUN: clang-doc --format=html --output=%t --executor=standalone %s // RUN: FileCheck %s < %t/html/GlobalNamespace/index.html enum Color { @@ -34,30 +34,26 @@ class Foo; // CHECK-NEXT:
    // CHECK-NEXT:
    // CHECK-NEXT:
    -// CHECK-NEXT:
    -// CHECK-NEXT:                      
    -// CHECK-NEXT:                          enum Color
    -// CHECK-NEXT:                      
    -// CHECK-NEXT:                  
    +// CHECK-NEXT:
    enum Color
    // CHECK-NEXT:
    // CHECK-NEXT: // CHECK-NEXT: -// CHECK-NEXT: -// CHECK-NEXT: -// CHECK-NEXT: -// CHECK: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: +// CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT: -// CHECK: +// CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT: -// CHECK: +// CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT: -// CHECK: +// CHECK-NEXT: // CHECK-NEXT: // CHECK-NEXT:
    NameValue
    NameValue
    RED0
    BLUE1
    GREEN2
    // CHECK-NEXT:
    diff --git a/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp b/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp index dfc81df13459..7fbf51c4efd3 100644 --- a/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp +++ b/clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp @@ -1,5 +1,5 @@ // RUN: rm -rf %t && mkdir -p %t -// RUN: clang-doc --format=mustache --output=%t --executor=standalone %s +// RUN: clang-doc --format=html --output=%t --executor=standalone %s // RUN: FileCheck %s < %t/html/MyNamespace/index.html // RUN: FileCheck %s < %t/html/GlobalNamespace/index.html --check-prefix=CHECK-GLOBAL @@ -19,13 +19,12 @@ namespace MyNamespace { // CHECK-GLOBAL:
    // CHECK-GLOBAL-NEXT:
    // CHECK-GLOBAL-NEXT: // CHECK-GLOBAL-NEXT:
    // CHECK-GLOBAL-NEXT:
    -// CHECK-GLOBAL-EMPTY: // CHECK-GLOBAL-NEXT:
    // CHECK-GLOBAL-NEXT:
    // CHECK-GLOBAL-NEXT:
    diff --git a/clang-tools-extra/test/clang-doc/namespace.cpp b/clang-tools-extra/test/clang-doc/namespace.cpp index adf7ab7d946a..8580ea6739a2 100644 --- a/clang-tools-extra/test/clang-doc/namespace.cpp +++ b/clang-tools-extra/test/clang-doc/namespace.cpp @@ -1,24 +1,6 @@ // RUN: rm -rf %t && mkdir -p %t // RUN: clang-doc --format=html --output=%t --executor=standalone %s // RUN: clang-doc --format=md --output=%t --executor=standalone %s -// RUN: clang-doc --format=mustache --output=%t --executor=standalone %s -// RUN: FileCheck %s < %t/index_json.js -check-prefix=JSON-INDEX -// RUN: FileCheck %s < %t/@nonymous_namespace/AnonClass.html -check-prefix=HTML-ANON-CLASS-LINE -// RUN: FileCheck %s < %t/@nonymous_namespace/AnonClass.html -check-prefix=HTML-ANON-CLASS -// RUN: FileCheck %s < %t/@nonymous_namespace/index.html -check-prefix=HTML-ANON-INDEX-LINE -// RUN: FileCheck %s < %t/@nonymous_namespace/index.html -check-prefix=HTML-ANON-INDEX -// RUN: FileCheck %s < %t/AnotherNamespace/ClassInAnotherNamespace.html -check-prefix=HTML-ANOTHER-CLASS-LINE -// RUN: FileCheck %s < %t/AnotherNamespace/ClassInAnotherNamespace.html -check-prefix=HTML-ANOTHER-CLASS -// RUN: FileCheck %s < %t/AnotherNamespace/index.html -check-prefix=HTML-ANOTHER-INDEX-LINE -// RUN: FileCheck %s < %t/AnotherNamespace/index.html -check-prefix=HTML-ANOTHER-INDEX -// RUN: FileCheck %s < %t/PrimaryNamespace/NestedNamespace/ClassInNestedNamespace.html -check-prefix=HTML-NESTED-CLASS-LINE -// RUN: FileCheck %s < %t/PrimaryNamespace/NestedNamespace/ClassInNestedNamespace.html -check-prefix=HTML-NESTED-CLASS -// RUN: FileCheck %s < %t/PrimaryNamespace/NestedNamespace/index.html -check-prefix=HTML-NESTED-INDEX-LINE -// RUN: FileCheck %s < %t/PrimaryNamespace/NestedNamespace/index.html -check-prefix=HTML-NESTED-INDEX -// RUN: FileCheck %s < %t/PrimaryNamespace/index.html -check-prefix=HTML-PRIMARY-INDEX-LINE -// RUN: FileCheck %s < %t/PrimaryNamespace/index.html -check-prefix=HTML-PRIMARY-INDEX -// RUN: FileCheck %s < %t/PrimaryNamespace/ClassInPrimaryNamespace.html -check-prefix=HTML-PRIMARY-CLASS-LINE -// RUN: FileCheck %s < %t/PrimaryNamespace/ClassInPrimaryNamespace.html -check-prefix=HTML-PRIMARY-CLASS // RUN: FileCheck %s < %t/@nonymous_namespace/AnonClass.md -check-prefix=MD-ANON-CLASS-LINE // RUN: FileCheck %s < %t/@nonymous_namespace/AnonClass.md -check-prefix=MD-ANON-CLASS // RUN: FileCheck %s < %t/@nonymous_namespace/index.md -check-prefix=MD-ANON-INDEX-LINE @@ -35,26 +17,26 @@ // RUN: FileCheck %s < %t/PrimaryNamespace/index.md -check-prefix=MD-PRIMARY-INDEX // RUN: FileCheck %s < %t/PrimaryNamespace/ClassInPrimaryNamespace.md -check-prefix=MD-PRIMARY-CLASS-LINE // RUN: FileCheck %s < %t/PrimaryNamespace/ClassInPrimaryNamespace.md -check-prefix=MD-PRIMARY-CLASS -// RUN: FileCheck %s < %t/GlobalNamespace/index.html -check-prefix=HTML-GLOBAL-INDEX +// RUN: FileCheck %s < %t/html/GlobalNamespace/index.html -check-prefix=HTML-GLOBAL-INDEX // RUN: FileCheck %s < %t/GlobalNamespace/index.md -check-prefix=MD-GLOBAL-INDEX // RUN: FileCheck %s < %t/all_files.md -check-prefix=MD-ALL-FILES // RUN: FileCheck %s < %t/index.md -check-prefix=MD-INDEX -// RUN: FileCheck %s < %t/html/@nonymous_namespace/_ZTVN12_GLOBAL__N_19AnonClassE.html -check-prefix=MUSTACHE-ANON-CLASS-LINE -// RUN: FileCheck %s < %t/html/@nonymous_namespace/_ZTVN12_GLOBAL__N_19AnonClassE.html -check-prefix=MUSTACHE-ANON-CLASS -// RUN: FileCheck %s < %t/html/@nonymous_namespace/index.html -check-prefix=MUSTACHE-ANON-INDEX-LINE -// RUN: FileCheck %s < %t/html/@nonymous_namespace/index.html -check-prefix=MUSTACHE-ANON-INDEX -// RUN: FileCheck %s < %t/html/AnotherNamespace/_ZTVN16AnotherNamespace23ClassInAnotherNamespaceE.html -check-prefix=MUSTACHE-ANOTHER-CLASS-LINE -// RUN: FileCheck %s < %t/html/AnotherNamespace/_ZTVN16AnotherNamespace23ClassInAnotherNamespaceE.html -check-prefix=MUSTACHE-ANOTHER-CLASS -// RUN: FileCheck %s < %t/html/AnotherNamespace/index.html -check-prefix=MUSTACHE-ANOTHER-INDEX-LINE -// RUN: FileCheck %s < %t/html/AnotherNamespace/index.html -check-prefix=MUSTACHE-ANOTHER-INDEX -// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/_ZTVN16PrimaryNamespace15NestedNamespace22ClassInNestedNamespaceE.html -check-prefix=MUSTACHE-NESTED-CLASS-LINE -// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/_ZTVN16PrimaryNamespace15NestedNamespace22ClassInNestedNamespaceE.html -check-prefix=MUSTACHE-NESTED-CLASS -// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/index.html -check-prefix=MUSTACHE-NESTED-INDEX-LINE -// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/index.html -check-prefix=MUSTACHE-NESTED-INDEX -// RUN: FileCheck %s < %t/html/PrimaryNamespace/index.html -check-prefix=MUSTACHE-PRIMARY-INDEX-LINE -// RUN: FileCheck %s < %t/html/PrimaryNamespace/index.html -check-prefix=MUSTACHE-PRIMARY-INDEX -// RUN: FileCheck %s < %t/html/PrimaryNamespace/_ZTVN16PrimaryNamespace23ClassInPrimaryNamespaceE.html -check-prefix=MUSTACHE-PRIMARY-CLASS-LINE -// RUN: FileCheck %s < %t/html/PrimaryNamespace/_ZTVN16PrimaryNamespace23ClassInPrimaryNamespaceE.html -check-prefix=MUSTACHE-PRIMARY-CLASS +// RUN: FileCheck %s < %t/html/@nonymous_namespace/_ZTVN12_GLOBAL__N_19AnonClassE.html -check-prefix=HTML-ANON-CLASS-LINE +// RUN: FileCheck %s < %t/html/@nonymous_namespace/_ZTVN12_GLOBAL__N_19AnonClassE.html -check-prefix=HTML-ANON-CLASS +// RUN: FileCheck %s < %t/html/@nonymous_namespace/index.html -check-prefix=HTML-ANON-INDEX-LINE +// RUN: FileCheck %s < %t/html/@nonymous_namespace/index.html -check-prefix=HTML-ANON-INDEX +// RUN: FileCheck %s < %t/html/AnotherNamespace/_ZTVN16AnotherNamespace23ClassInAnotherNamespaceE.html -check-prefix=HTML-ANOTHER-CLASS-LINE +// RUN: FileCheck %s < %t/html/AnotherNamespace/_ZTVN16AnotherNamespace23ClassInAnotherNamespaceE.html -check-prefix=HTML-ANOTHER-CLASS +// RUN: FileCheck %s < %t/html/AnotherNamespace/index.html -check-prefix=HTML-ANOTHER-INDEX-LINE +// RUN: FileCheck %s < %t/html/AnotherNamespace/index.html -check-prefix=HTML-ANOTHER-INDEX +// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/_ZTVN16PrimaryNamespace15NestedNamespace22ClassInNestedNamespaceE.html -check-prefix=HTML-NESTED-CLASS-LINE +// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/_ZTVN16PrimaryNamespace15NestedNamespace22ClassInNestedNamespaceE.html -check-prefix=HTML-NESTED-CLASS +// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/index.html -check-prefix=HTML-NESTED-INDEX-LINE +// RUN: FileCheck %s < %t/html/PrimaryNamespace/NestedNamespace/index.html -check-prefix=HTML-NESTED-INDEX +// RUN: FileCheck %s < %t/html/PrimaryNamespace/index.html -check-prefix=HTML-PRIMARY-INDEX-LINE +// RUN: FileCheck %s < %t/html/PrimaryNamespace/index.html -check-prefix=HTML-PRIMARY-INDEX +// RUN: FileCheck %s < %t/html/PrimaryNamespace/_ZTVN16PrimaryNamespace23ClassInPrimaryNamespaceE.html -check-prefix=HTML-PRIMARY-CLASS-LINE +// RUN: FileCheck %s < %t/html/PrimaryNamespace/_ZTVN16PrimaryNamespace23ClassInPrimaryNamespaceE.html -check-prefix=HTML-PRIMARY-CLASS // COM: FIXME: Add global functions to the namespace template // COM: FIXME: Add namespaces to the namespace template @@ -63,17 +45,14 @@ namespace { void anonFunction() {} // MD-ANON-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp#[[@LINE-1]]* -// HTML-ANON-INDEX-LINE:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp

    -// MUSTACHE-ANON-INDEX-LINE-NOT:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp

    +// HTML-ANON-INDEX-LINE-NOT:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp

    class AnonClass {}; // MD-ANON-CLASS-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp#[[@LINE-1]]* // HTML-ANON-CLASS-LINE:

    Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp

    -// MUSTACHE-ANON-CLASS-LINE:

    Defined at line [[@LINE-3]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp

    // MD-ANON-CLASS: # class AnonClass -// HTML-ANON-CLASS:

    class AnonClass

    -// MUSTACHE-ANON-CLASS:

    class AnonClass

    +// HTML-ANON-CLASS:

    class AnonClass

    } // namespace // MD-ANON-INDEX: # namespace @nonymous_namespace @@ -84,69 +63,51 @@ class AnonClass {}; // MD-ANON-INDEX: ### anonFunction // MD-ANON-INDEX: *void anonFunction()* -// HTML-ANON-INDEX:

    namespace @nonymous_namespace

    -// HTML-ANON-INDEX:

    Anonymous Namespace

    -// HTML-ANON-INDEX:

    Records

    -// HTML-ANON-INDEX: AnonClass -// HTML-ANON-INDEX:

    Functions

    -// HTML-ANON-INDEX:

    anonFunction

    -// HTML-ANON-INDEX:

    void anonFunction()

    - -// MUSTACHE-ANON-INDEX:

    @nonymous_namespace

    -// MUSTACHE-ANON-INDEX:

    Inner Classes

    -// MUSTACHE-ANON-INDEX: