| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
|
|
||
| # REQUIRES: system-linux | ||
|
|
||
| # RUN: llvm-mc -dwarf-version=4 -filetype=obj -triple x86_64-unknown-linux %s -o %tmain.o | ||
| # RUN: %clang %cflags -dwarf-4 %tmain.o -o %t.exe -Wl,-q | ||
| # RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections | ||
| # RUN: llvm-dwarfdump --show-form --verbose --debug-info %t.exe | FileCheck --check-prefix=PRECHECK %s | ||
| # RUN: llvm-dwarfdump --show-form --verbose --debug-info %t.bolt > %t.txt | ||
| # RUN: llvm-objdump -d %t.bolt >> %t.txt | ||
| # RUN: cat %t.txt | FileCheck --check-prefix=POSTCHECK %s | ||
|
|
||
| ## This test checks that we correctly handle DW_AT_low_pc [DW_FORM_addr] that is part of DW_TAG_label. | ||
|
|
||
| # PRECHECK: version = 0x0004 | ||
| # PRECHECK: DW_TAG_label | ||
| # PRECHECK-NEXT: DW_AT_name | ||
| # PRECHECK-NEXT: DW_AT_decl_file | ||
| # PRECHECK-NEXT: DW_AT_decl_line | ||
| # PRECHECK-NEXT:DW_AT_low_pc [DW_FORM_addr] | ||
| # PRECHECK: DW_TAG_label | ||
| # PRECHECK-NEXT: DW_AT_name | ||
| # PRECHECK-NEXT: DW_AT_decl_file | ||
| # PRECHECK-NEXT: DW_AT_decl_line | ||
| # PRECHECK-NEXT:DW_AT_low_pc [DW_FORM_addr] | ||
|
|
||
| # POSTCHECK: version = 0x0004 | ||
| # POSTCHECK: DW_TAG_label | ||
| # POSTCHECK-NEXT: DW_AT_name | ||
| # POSTCHECK-NEXT: DW_AT_decl_file | ||
| # POSTCHECK-NEXT: DW_AT_decl_line | ||
| # POSTCHECK-NEXT:DW_AT_low_pc [DW_FORM_addr] (0x[[ADDR:[1-9a-f]*]] | ||
| # POSTCHECK: DW_TAG_label | ||
| # POSTCHECK-NEXT: DW_AT_name | ||
| # POSTCHECK-NEXT: DW_AT_decl_file | ||
| # POSTCHECK-NEXT: DW_AT_decl_line | ||
| # POSTCHECK-NEXT:DW_AT_low_pc [DW_FORM_addr] (0x[[ADDR2:[1-9a-f]*]] | ||
|
|
||
| # POSTCHECK: [[ADDR]]: 8b 45 f8 | ||
| # POSTCHECK: [[ADDR2]]: 8b 45 f8 | ||
|
|
||
| ## clang++ main.cpp -g2 -gdwarf-4 -S | ||
| ## int main() { | ||
| ## int a = 4; | ||
| ## if (a == 5) | ||
| ## goto LABEL1; | ||
| ## else | ||
| ## goto LABEL2; | ||
| ## LABEL1:a++; | ||
| ## LABEL2:a--; | ||
| ## return 0; | ||
| ## } | ||
|
|
||
| .text | ||
| .file "main.cpp" | ||
| .globl main # -- Begin function main | ||
| .p2align 4, 0x90 | ||
| .type main,@function | ||
| main: # @main | ||
| .Lfunc_begin0: | ||
| .file 1 "/home" "main.cpp" | ||
| .loc 1 1 0 # main.cpp:1:0 | ||
| .cfi_startproc | ||
| # %bb.0: # %entry | ||
| pushq %rbp | ||
| .cfi_def_cfa_offset 16 | ||
| .cfi_offset %rbp, -16 | ||
| movq %rsp, %rbp | ||
| .cfi_def_cfa_register %rbp | ||
| movl $0, -4(%rbp) | ||
| .Ltmp0: | ||
| .loc 1 2 7 prologue_end # main.cpp:2:7 | ||
| movl $4, -8(%rbp) | ||
| .Ltmp1: | ||
| .loc 1 3 9 # main.cpp:3:9 | ||
| cmpl $5, -8(%rbp) | ||
| .Ltmp2: | ||
| .loc 1 3 7 is_stmt 0 # main.cpp:3:7 | ||
| jne .LBB0_2 | ||
| # %bb.1: # %if.then | ||
| .Ltmp3: | ||
| .loc 1 4 5 is_stmt 1 # main.cpp:4:5 | ||
| jmp .LBB0_3 | ||
| .LBB0_2: # %if.else | ||
| .loc 1 6 5 # main.cpp:6:5 | ||
| jmp .LBB0_4 | ||
| .Ltmp4: | ||
| .LBB0_3: # %LABEL1 | ||
| #DEBUG_LABEL: main:LABEL1 | ||
| .loc 1 7 11 # main.cpp:7:11 | ||
| movl -8(%rbp), %eax | ||
| addl $1, %eax | ||
| movl %eax, -8(%rbp) | ||
| .LBB0_4: # %LABEL2 | ||
| .Ltmp5: | ||
| #DEBUG_LABEL: main:LABEL2 | ||
| .loc 1 8 11 # main.cpp:8:11 | ||
| movl -8(%rbp), %eax | ||
| addl $-1, %eax | ||
| movl %eax, -8(%rbp) | ||
| .loc 1 9 3 # main.cpp:9:3 | ||
| xorl %eax, %eax | ||
| .loc 1 9 3 epilogue_begin is_stmt 0 # main.cpp:9:3 | ||
| popq %rbp | ||
| .cfi_def_cfa %rsp, 8 | ||
| retq | ||
| .Ltmp6: | ||
| .Lfunc_end0: | ||
| .size main, .Lfunc_end0-main | ||
| .cfi_endproc | ||
| # -- End function | ||
| .section .debug_abbrev,"",@progbits | ||
| .byte 1 # Abbreviation Code | ||
| .byte 17 # DW_TAG_compile_unit | ||
| .byte 1 # DW_CHILDREN_yes | ||
| .byte 37 # DW_AT_producer | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 19 # DW_AT_language | ||
| .byte 5 # DW_FORM_data2 | ||
| .byte 3 # DW_AT_name | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 16 # DW_AT_stmt_list | ||
| .byte 23 # DW_FORM_sec_offset | ||
| .byte 27 # DW_AT_comp_dir | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 17 # DW_AT_low_pc | ||
| .byte 1 # DW_FORM_addr | ||
| .byte 18 # DW_AT_high_pc | ||
| .byte 6 # DW_FORM_data4 | ||
| .byte 0 # EOM(1) | ||
| .byte 0 # EOM(2) | ||
| .byte 2 # Abbreviation Code | ||
| .byte 46 # DW_TAG_subprogram | ||
| .byte 1 # DW_CHILDREN_yes | ||
| .byte 17 # DW_AT_low_pc | ||
| .byte 1 # DW_FORM_addr | ||
| .byte 18 # DW_AT_high_pc | ||
| .byte 6 # DW_FORM_data4 | ||
| .byte 64 # DW_AT_frame_base | ||
| .byte 24 # DW_FORM_exprloc | ||
| .byte 3 # DW_AT_name | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 58 # DW_AT_decl_file | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 59 # DW_AT_decl_line | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 73 # DW_AT_type | ||
| .byte 19 # DW_FORM_ref4 | ||
| .byte 63 # DW_AT_external | ||
| .byte 25 # DW_FORM_flag_present | ||
| .byte 0 # EOM(1) | ||
| .byte 0 # EOM(2) | ||
| .byte 3 # Abbreviation Code | ||
| .byte 52 # DW_TAG_variable | ||
| .byte 0 # DW_CHILDREN_no | ||
| .byte 2 # DW_AT_location | ||
| .byte 24 # DW_FORM_exprloc | ||
| .byte 3 # DW_AT_name | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 58 # DW_AT_decl_file | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 59 # DW_AT_decl_line | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 73 # DW_AT_type | ||
| .byte 19 # DW_FORM_ref4 | ||
| .byte 0 # EOM(1) | ||
| .byte 0 # EOM(2) | ||
| .byte 4 # Abbreviation Code | ||
| .byte 10 # DW_TAG_label | ||
| .byte 0 # DW_CHILDREN_no | ||
| .byte 3 # DW_AT_name | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 58 # DW_AT_decl_file | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 59 # DW_AT_decl_line | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 17 # DW_AT_low_pc | ||
| .byte 1 # DW_FORM_addr | ||
| .byte 0 # EOM(1) | ||
| .byte 0 # EOM(2) | ||
| .byte 5 # Abbreviation Code | ||
| .byte 36 # DW_TAG_base_type | ||
| .byte 0 # DW_CHILDREN_no | ||
| .byte 3 # DW_AT_name | ||
| .byte 14 # DW_FORM_strp | ||
| .byte 62 # DW_AT_encoding | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 11 # DW_AT_byte_size | ||
| .byte 11 # DW_FORM_data1 | ||
| .byte 0 # EOM(1) | ||
| .byte 0 # EOM(2) | ||
| .byte 0 # EOM(3) | ||
| .section .debug_info,"",@progbits | ||
| .Lcu_begin0: | ||
| .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit | ||
| .Ldebug_info_start0: | ||
| .short 4 # DWARF version number | ||
| .long .debug_abbrev # Offset Into Abbrev. Section | ||
| .byte 8 # Address Size (in bytes) | ||
| .byte 1 # Abbrev [1] 0xb:0x6d DW_TAG_compile_unit | ||
| .long .Linfo_string0 # DW_AT_producer | ||
| .short 33 # DW_AT_language | ||
| .long .Linfo_string1 # DW_AT_name | ||
| .long .Lline_table_start0 # DW_AT_stmt_list | ||
| .long .Linfo_string2 # DW_AT_comp_dir | ||
| .quad .Lfunc_begin0 # DW_AT_low_pc | ||
| .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc | ||
| .byte 2 # Abbrev [2] 0x2a:0x46 DW_TAG_subprogram | ||
| .quad .Lfunc_begin0 # DW_AT_low_pc | ||
| .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc | ||
| .byte 1 # DW_AT_frame_base | ||
| .byte 86 | ||
| .long .Linfo_string3 # DW_AT_name | ||
| .byte 1 # DW_AT_decl_file | ||
| .byte 1 # DW_AT_decl_line | ||
| .long 112 # DW_AT_type | ||
| # DW_AT_external | ||
| .byte 3 # Abbrev [3] 0x43:0xe DW_TAG_variable | ||
| .byte 2 # DW_AT_location | ||
| .byte 145 | ||
| .byte 120 | ||
| .long .Linfo_string5 # DW_AT_name | ||
| .byte 1 # DW_AT_decl_file | ||
| .byte 2 # DW_AT_decl_line | ||
| .long 112 # DW_AT_type | ||
| .byte 4 # Abbrev [4] 0x51:0xf DW_TAG_label | ||
| .long .Linfo_string6 # DW_AT_name | ||
| .byte 1 # DW_AT_decl_file | ||
| .byte 7 # DW_AT_decl_line | ||
| .quad .Ltmp4 # DW_AT_low_pc | ||
| .byte 4 # Abbrev [4] 0x60:0xf DW_TAG_label | ||
| .long .Linfo_string7 # DW_AT_name | ||
| .byte 1 # DW_AT_decl_file | ||
| .byte 8 # DW_AT_decl_line | ||
| .quad .Ltmp5 # DW_AT_low_pc | ||
| .byte 0 # End Of Children Mark | ||
| .byte 5 # Abbrev [5] 0x70:0x7 DW_TAG_base_type | ||
| .long .Linfo_string4 # DW_AT_name | ||
| .byte 5 # DW_AT_encoding | ||
| .byte 4 # DW_AT_byte_size | ||
| .byte 0 # End Of Children Mark | ||
| .Ldebug_info_end0: | ||
| .section .debug_str,"MS",@progbits,1 | ||
| .Linfo_string0: | ||
| .asciz "clang version 19.0.0git" # string offset=0 | ||
| .Linfo_string1: | ||
| .asciz "main.cpp" # string offset=24 | ||
| .Linfo_string2: | ||
| .asciz "/home" # string offset=33 | ||
| .Linfo_string3: | ||
| .asciz "main" # string offset=71 | ||
| .Linfo_string4: | ||
| .asciz "int" # string offset=76 | ||
| .Linfo_string5: | ||
| .asciz "a" # string offset=80 | ||
| .Linfo_string6: | ||
| .asciz "LABEL1" # string offset=82 | ||
| .Linfo_string7: | ||
| .asciz "LABEL2" # string offset=89 | ||
| .ident "clang version 19.0.0git" | ||
| .section ".note.GNU-stack","",@progbits | ||
| .addrsig | ||
| .section .debug_line,"",@progbits | ||
| .Lline_table_start0: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| ## This reproduces a bug with BOLT setting incorrect discriminator for | ||
| ## secondary entry points in YAML profile. | ||
|
|
||
| # REQUIRES: system-linux | ||
| # RUN: llvm-mc -filetype=obj -triple x86_64-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 -Wl,-q -nostdlib | ||
| # RUN: llvm-bolt %t.exe -o %t.out --data %t.fdata -w %t.yaml --print-profile \ | ||
| # RUN: --print-only=main | FileCheck %s --check-prefix=CHECK-CFG | ||
| # RUN: FileCheck %s -input-file %t.yaml | ||
| # CHECK: - name: main | ||
| # CHECK-NEXT: fid: 2 | ||
| # CHECK-NEXT: hash: {{.*}} | ||
| # CHECK-NEXT: exec: 0 | ||
| # CHECK-NEXT: nblocks: 4 | ||
| # CHECK-NEXT: blocks: | ||
| # CHECK: - bid: 1 | ||
| # CHECK-NEXT: insns: 1 | ||
| # CHECK-NEXT: hash: {{.*}} | ||
| # CHECK-NEXT: calls: [ { off: 0x0, fid: 1, disc: 1, cnt: 1 } ] | ||
| # CHECK: - bid: 2 | ||
| # CHECK-NEXT: insns: 5 | ||
| # CHECK-NEXT: hash: {{.*}} | ||
| # CHECK-NEXT: calls: [ { off: 0x0, fid: 1, disc: 1, cnt: 1, mis: 1 } ] | ||
|
|
||
| ## Make sure that the profile is attached correctly | ||
| # RUN: llvm-bolt %t.exe -o %t.out --data %t.yaml --print-profile \ | ||
| # RUN: --print-only=main | FileCheck %s --check-prefix=CHECK-CFG | ||
|
|
||
| # CHECK-CFG: Binary Function "main" after attaching profile { | ||
| # CHECK-CFG: callq secondary_entry # Offset: [[#]] # Count: 1 | ||
| # CHECK-CFG: callq *%rax # Offset: [[#]] # CallProfile: 1 (1 misses) : | ||
| # CHECK-CFG-NEXT: { secondary_entry: 1 (1 misses) } | ||
|
|
||
| ## YAML BAT test of calling BAT secondary entry from non-BAT function | ||
| ## Now force-split func and skip main (making it call secondary entries) | ||
| # RUN: llvm-bolt %t.exe -o %t.bat --data %t.fdata --funcs=func \ | ||
| # RUN: --split-functions --split-strategy=all --split-all-cold --enable-bat | ||
|
|
||
| ## Prepare pre-aggregated profile using %t.bat | ||
| # RUN: link_fdata %s %t.bat %t.preagg PREAGG | ||
| ## Strip labels used for pre-aggregated profile | ||
| # RUN: llvm-strip -NLcall -NLindcall %t.bat | ||
|
|
||
| ## Convert pre-aggregated profile using BAT | ||
| # RUN: perf2bolt %t.bat -p %t.preagg --pa -o %t.bat.fdata -w %t.bat.yaml | ||
|
|
||
| ## Convert BAT fdata into YAML | ||
| # RUN: llvm-bolt %t.exe -data %t.bat.fdata -w %t.bat.fdata-yaml -o /dev/null | ||
|
|
||
| ## Check fdata YAML - make sure that a direct call has discriminator field | ||
| # RUN: FileCheck %s --input-file %t.bat.fdata-yaml -check-prefix CHECK-BAT-YAML | ||
|
|
||
| ## Check BAT YAML - make sure that a direct call has discriminator field | ||
| # RUN: FileCheck %s --input-file %t.bat.yaml --check-prefix CHECK-BAT-YAML | ||
|
|
||
| ## YAML BAT test of calling BAT secondary entry from BAT function | ||
| # RUN: llvm-bolt %t.exe -o %t.bat2 --data %t.fdata --funcs=main,func \ | ||
| # RUN: --split-functions --split-strategy=all --split-all-cold --enable-bat | ||
|
|
||
| ## Prepare pre-aggregated profile using %t.bat | ||
| # RUN: link_fdata %s %t.bat2 %t.preagg2 PREAGG2 | ||
|
|
||
| ## Strip labels used for pre-aggregated profile | ||
| # RUN: llvm-strip -NLcall -NLindcall %t.bat2 | ||
|
|
||
| ## Convert pre-aggregated profile using BAT | ||
| # RUN: perf2bolt %t.bat2 -p %t.preagg2 --pa -o %t.bat2.fdata -w %t.bat2.yaml | ||
|
|
||
| ## Convert BAT fdata into YAML | ||
| # RUN: llvm-bolt %t.exe -data %t.bat2.fdata -w %t.bat2.fdata-yaml -o /dev/null | ||
|
|
||
| ## Check fdata YAML - make sure that a direct call has discriminator field | ||
| # RUN: FileCheck %s --input-file %t.bat2.fdata-yaml -check-prefix CHECK-BAT-YAML | ||
|
|
||
| ## Check BAT YAML - make sure that a direct call has discriminator field | ||
| # RUN: FileCheck %s --input-file %t.bat2.yaml --check-prefix CHECK-BAT-YAML | ||
|
|
||
| # CHECK-BAT-YAML: - name: main | ||
| # CHECK-BAT-YAML-NEXT: fid: [[#]] | ||
| # CHECK-BAT-YAML-NEXT: hash: 0xADF270D550151185 | ||
| # CHECK-BAT-YAML-NEXT: exec: 0 | ||
| # CHECK-BAT-YAML-NEXT: nblocks: 4 | ||
| # CHECK-BAT-YAML-NEXT: blocks: | ||
| # CHECK-BAT-YAML: - bid: 1 | ||
| # CHECK-BAT-YAML-NEXT: insns: [[#]] | ||
| # CHECK-BAT-YAML-NEXT: hash: 0x36A303CBA4360018 | ||
| # CHECK-BAT-YAML-NEXT: calls: [ { off: 0x0, fid: [[#]], disc: 1, cnt: 1 | ||
|
|
||
| .globl func | ||
| .type func, @function | ||
| func: | ||
| # FDATA: 0 [unknown] 0 1 func 0 1 0 | ||
| # PREAGG: B X:0 #func# 1 1 | ||
| # PREAGG2: B X:0 #func# 1 1 | ||
| .cfi_startproc | ||
| pushq %rbp | ||
| movq %rsp, %rbp | ||
| ## Placeholder code to make splitting profitable | ||
| .rept 5 | ||
| testq %rax, %rax | ||
| .endr | ||
| .globl secondary_entry | ||
| secondary_entry: | ||
| ## Placeholder code to make splitting profitable | ||
| .rept 5 | ||
| testq %rax, %rax | ||
| .endr | ||
| popq %rbp | ||
| retq | ||
| nopl (%rax) | ||
| .cfi_endproc | ||
| .size func, .-func | ||
|
|
||
| .globl main | ||
| .type main, @function | ||
| main: | ||
| .cfi_startproc | ||
| pushq %rbp | ||
| movq %rsp, %rbp | ||
| subq $16, %rsp | ||
| movl $0, -4(%rbp) | ||
| testq %rax, %rax | ||
| jne Lindcall | ||
| .globl Lcall | ||
| Lcall: | ||
| call secondary_entry | ||
| # FDATA: 1 main #Lcall# 1 secondary_entry 0 1 1 | ||
| # PREAGG: B #Lcall# #secondary_entry# 1 1 | ||
| # PREAGG2: B #main.cold.0# #func.cold.0# 1 1 | ||
| .globl Lindcall | ||
| Lindcall: | ||
| callq *%rax | ||
| # FDATA: 1 main #Lindcall# 1 secondary_entry 0 1 1 | ||
| # PREAGG: B #Lindcall# #secondary_entry# 1 1 | ||
| # PREAGG2: B #main.cold.1# #func.cold.0# 1 1 | ||
| xorl %eax, %eax | ||
| addq $16, %rsp | ||
| popq %rbp | ||
| retq | ||
| ## For relocations against .text | ||
| call exit | ||
| .cfi_endproc | ||
| .size main, .-main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| # REQUIRES: system-linux | ||
|
|
||
| # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o | ||
| # RUN: llvm-strip --strip-unneeded %t.o | ||
| # RUN: %clang %cflags -no-pie -nostartfiles -nostdlib -lc %t.o -o %t.exe -Wl,-q | ||
|
|
||
| # RUN: llvm-bolt %t.exe -o %t.exe.bolt --relocs=1 --lite=0 | ||
|
|
||
| # RUN: %t.exe.bolt | ||
|
|
||
| ## Check that BOLT's jump table detection diffrentiates between | ||
| ## __builtin_unreachable() targets and function pointers. | ||
|
|
||
| ## The test case was built from the following two source files and | ||
| ## modiffied for standalone build. main became _start, etc. | ||
| ## $ $(CC) a.c -O1 -S -o a.s | ||
| ## $ $(CC) b.c -O0 -S -o b.s | ||
|
|
||
| ## a.c: | ||
|
|
||
| ## typedef int (*fptr)(int); | ||
| ## void check_fptr(fptr, int); | ||
| ## | ||
| ## int foo(int a) { | ||
| ## check_fptr(foo, 0); | ||
| ## switch (a) { | ||
| ## default: | ||
| ## __builtin_unreachable(); | ||
| ## case 0: | ||
| ## return 3; | ||
| ## case 1: | ||
| ## return 5; | ||
| ## case 2: | ||
| ## return 7; | ||
| ## case 3: | ||
| ## return 11; | ||
| ## case 4: | ||
| ## return 13; | ||
| ## case 5: | ||
| ## return 17; | ||
| ## } | ||
| ## return 0; | ||
| ## } | ||
| ## | ||
| ## int main(int argc) { | ||
| ## check_fptr(main, 1); | ||
| ## return foo(argc); | ||
| ## } | ||
| ## | ||
| ## const fptr funcs[2] = {foo, main}; | ||
|
|
||
| ## b.c.: | ||
|
|
||
| ## typedef int (*fptr)(int); | ||
| ## extern const fptr funcs[2]; | ||
| ## | ||
| ## #define assert(C) { if (!(C)) (*(unsigned long long *)0) = 0; } | ||
| ## void check_fptr(fptr f, int i) { | ||
| ## assert(f == funcs[i]); | ||
| ## } | ||
|
|
||
|
|
||
| .text | ||
| .globl foo | ||
| .type foo, @function | ||
| foo: | ||
| .LFB0: | ||
| .cfi_startproc | ||
| pushq %rbx | ||
| .cfi_def_cfa_offset 16 | ||
| .cfi_offset 3, -16 | ||
| movl %edi, %ebx | ||
| movl $0, %esi | ||
| movl $foo, %edi | ||
| call check_fptr | ||
| movl %ebx, %ebx | ||
| jmp *.L4(,%rbx,8) | ||
| .L8: | ||
| movl $5, %eax | ||
| jmp .L1 | ||
| .L7: | ||
| movl $7, %eax | ||
| jmp .L1 | ||
| .L6: | ||
| movl $11, %eax | ||
| jmp .L1 | ||
| .L5: | ||
| movl $13, %eax | ||
| jmp .L1 | ||
| .L3: | ||
| movl $17, %eax | ||
| jmp .L1 | ||
| .L10: | ||
| movl $3, %eax | ||
| .L1: | ||
| popq %rbx | ||
| .cfi_def_cfa_offset 8 | ||
| ret | ||
| .cfi_endproc | ||
| .LFE0: | ||
| .size foo, .-foo | ||
| .globl _start | ||
| .type _start, @function | ||
| _start: | ||
| .LFB1: | ||
| .cfi_startproc | ||
| pushq %rbx | ||
| .cfi_def_cfa_offset 16 | ||
| .cfi_offset 3, -16 | ||
| movl %edi, %ebx | ||
| movl $1, %esi | ||
| movl $_start, %edi | ||
| call check_fptr | ||
| movl $1, %edi | ||
| call foo | ||
| popq %rbx | ||
| .cfi_def_cfa_offset 8 | ||
| callq exit@PLT | ||
| .cfi_endproc | ||
| .LFE1: | ||
| .size _start, .-_start | ||
| .globl check_fptr | ||
| .type check_fptr, @function | ||
| check_fptr: | ||
| .LFB2: | ||
| .cfi_startproc | ||
| pushq %rbp | ||
| .cfi_def_cfa_offset 16 | ||
| .cfi_offset 6, -16 | ||
| movq %rsp, %rbp | ||
| .cfi_def_cfa_register 6 | ||
| movq %rdi, -8(%rbp) | ||
| movl %esi, -12(%rbp) | ||
| movl -12(%rbp), %eax | ||
| cltq | ||
| movq funcs(,%rax,8), %rax | ||
| cmpq %rax, -8(%rbp) | ||
| je .L33 | ||
| movl $0, %eax | ||
| movq $0, (%rax) | ||
| .L33: | ||
| nop | ||
| popq %rbp | ||
| .cfi_def_cfa 7, 8 | ||
| ret | ||
| .cfi_endproc | ||
|
|
||
| .section .rodata | ||
| .align 8 | ||
| .align 4 | ||
| .L4: | ||
| .quad .L10 | ||
| .quad .L8 | ||
| .quad .L7 | ||
| .quad .L6 | ||
| .quad .L5 | ||
| .quad .L3 | ||
|
|
||
| .globl funcs | ||
| .type funcs, @object | ||
| .size funcs, 16 | ||
| funcs: | ||
| .quad foo | ||
| .quad _start |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| //===--- EnumInitialValueCheck.cpp - clang-tidy ---------------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "EnumInitialValueCheck.h" | ||
| #include "../utils/LexerUtils.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/ADT/SmallString.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::readability { | ||
|
|
||
| static bool isNoneEnumeratorsInitialized(const EnumDecl &Node) { | ||
| return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { | ||
| return ECD->getInitExpr() == nullptr; | ||
| }); | ||
| } | ||
|
|
||
| static bool isOnlyFirstEnumeratorInitialized(const EnumDecl &Node) { | ||
| bool IsFirst = true; | ||
| for (const EnumConstantDecl *ECD : Node.enumerators()) { | ||
| if ((IsFirst && ECD->getInitExpr() == nullptr) || | ||
| (!IsFirst && ECD->getInitExpr() != nullptr)) | ||
| return false; | ||
| IsFirst = false; | ||
| } | ||
| return !IsFirst; | ||
| } | ||
|
|
||
| static bool areAllEnumeratorsInitialized(const EnumDecl &Node) { | ||
| return llvm::all_of(Node.enumerators(), [](const EnumConstantDecl *ECD) { | ||
| return ECD->getInitExpr() != nullptr; | ||
| }); | ||
| } | ||
|
|
||
| /// Check if \p Enumerator is initialized with a (potentially negated) \c | ||
| /// IntegerLiteral. | ||
| static bool isInitializedByLiteral(const EnumConstantDecl *Enumerator) { | ||
| const Expr *const Init = Enumerator->getInitExpr(); | ||
| if (!Init) | ||
| return false; | ||
| return Init->isIntegerConstantExpr(Enumerator->getASTContext()); | ||
| } | ||
|
|
||
| static void cleanInitialValue(DiagnosticBuilder &Diag, | ||
| const EnumConstantDecl *ECD, | ||
| const SourceManager &SM, | ||
| const LangOptions &LangOpts) { | ||
| const SourceRange InitExprRange = ECD->getInitExpr()->getSourceRange(); | ||
| if (InitExprRange.isInvalid() || InitExprRange.getBegin().isMacroID() || | ||
| InitExprRange.getEnd().isMacroID()) | ||
| return; | ||
| std::optional<Token> EqualToken = utils::lexer::findNextTokenSkippingComments( | ||
| ECD->getLocation(), SM, LangOpts); | ||
| if (!EqualToken.has_value() || | ||
| EqualToken.value().getKind() != tok::TokenKind::equal) | ||
| return; | ||
| const SourceLocation EqualLoc{EqualToken->getLocation()}; | ||
| if (EqualLoc.isInvalid() || EqualLoc.isMacroID()) | ||
| return; | ||
| Diag << FixItHint::CreateRemoval(EqualLoc) | ||
| << FixItHint::CreateRemoval(InitExprRange); | ||
| return; | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| AST_MATCHER(EnumDecl, isMacro) { | ||
| SourceLocation Loc = Node.getBeginLoc(); | ||
| return Loc.isMacroID(); | ||
| } | ||
|
|
||
| AST_MATCHER(EnumDecl, hasConsistentInitialValues) { | ||
| return isNoneEnumeratorsInitialized(Node) || | ||
| isOnlyFirstEnumeratorInitialized(Node) || | ||
| areAllEnumeratorsInitialized(Node); | ||
| } | ||
|
|
||
| AST_MATCHER(EnumDecl, hasZeroInitialValueForFirstEnumerator) { | ||
| const EnumDecl::enumerator_range Enumerators = Node.enumerators(); | ||
| if (Enumerators.empty()) | ||
| return false; | ||
| const EnumConstantDecl *ECD = *Enumerators.begin(); | ||
| return isOnlyFirstEnumeratorInitialized(Node) && | ||
| isInitializedByLiteral(ECD) && ECD->getInitVal().isZero(); | ||
| } | ||
|
|
||
| /// Excludes bitfields because enumerators initialized with the result of a | ||
| /// bitwise operator on enumeration values or any other expr that is not a | ||
| /// potentially negative integer literal. | ||
| /// Enumerations where it is not directly clear if they are used with | ||
| /// bitmask, evident when enumerators are only initialized with (potentially | ||
| /// negative) integer literals, are ignored. This is also the case when all | ||
| /// enumerators are powers of two (e.g., 0, 1, 2). | ||
| AST_MATCHER(EnumDecl, hasSequentialInitialValues) { | ||
| const EnumDecl::enumerator_range Enumerators = Node.enumerators(); | ||
| if (Enumerators.empty()) | ||
| return false; | ||
| const EnumConstantDecl *const FirstEnumerator = *Node.enumerator_begin(); | ||
| llvm::APSInt PrevValue = FirstEnumerator->getInitVal(); | ||
| if (!isInitializedByLiteral(FirstEnumerator)) | ||
| return false; | ||
| bool AllEnumeratorsArePowersOfTwo = true; | ||
| for (const EnumConstantDecl *Enumerator : llvm::drop_begin(Enumerators)) { | ||
| const llvm::APSInt NewValue = Enumerator->getInitVal(); | ||
| if (NewValue != ++PrevValue) | ||
| return false; | ||
| if (!isInitializedByLiteral(Enumerator)) | ||
| return false; | ||
| PrevValue = NewValue; | ||
| AllEnumeratorsArePowersOfTwo &= NewValue.isPowerOf2(); | ||
| } | ||
| return !AllEnumeratorsArePowersOfTwo; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| EnumInitialValueCheck::EnumInitialValueCheck(StringRef Name, | ||
| ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| AllowExplicitZeroFirstInitialValue( | ||
| Options.get("AllowExplicitZeroFirstInitialValue", true)), | ||
| AllowExplicitSequentialInitialValues( | ||
| Options.get("AllowExplicitSequentialInitialValues", true)) {} | ||
|
|
||
| void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, "AllowExplicitZeroFirstInitialValue", | ||
| AllowExplicitZeroFirstInitialValue); | ||
| Options.store(Opts, "AllowExplicitSequentialInitialValues", | ||
| AllowExplicitSequentialInitialValues); | ||
| } | ||
|
|
||
| void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) { | ||
| Finder->addMatcher( | ||
| enumDecl(unless(isMacro()), unless(hasConsistentInitialValues())) | ||
| .bind("inconsistent"), | ||
| this); | ||
| if (!AllowExplicitZeroFirstInitialValue) | ||
| Finder->addMatcher( | ||
| enumDecl(hasZeroInitialValueForFirstEnumerator()).bind("zero_first"), | ||
| this); | ||
| if (!AllowExplicitSequentialInitialValues) | ||
| Finder->addMatcher(enumDecl(unless(isMacro()), hasSequentialInitialValues()) | ||
| .bind("sequential"), | ||
| this); | ||
| } | ||
|
|
||
| void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) { | ||
| if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) { | ||
| DiagnosticBuilder Diag = | ||
| diag(Enum->getBeginLoc(), | ||
| "inital values in enum %0 are not consistent, consider explicit " | ||
| "initialization of all, none or only the first enumerator") | ||
| << Enum; | ||
| for (const EnumConstantDecl *ECD : Enum->enumerators()) | ||
| if (ECD->getInitExpr() == nullptr) { | ||
| const SourceLocation EndLoc = Lexer::getLocForEndOfToken( | ||
| ECD->getLocation(), 0, *Result.SourceManager, getLangOpts()); | ||
| if (EndLoc.isMacroID()) | ||
| continue; | ||
| llvm::SmallString<8> Str{" = "}; | ||
| ECD->getInitVal().toString(Str); | ||
| Diag << FixItHint::CreateInsertion(EndLoc, Str); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("zero_first")) { | ||
| const EnumConstantDecl *ECD = *Enum->enumerator_begin(); | ||
| const SourceLocation Loc = ECD->getLocation(); | ||
| if (Loc.isInvalid() || Loc.isMacroID()) | ||
| return; | ||
| DiagnosticBuilder Diag = diag(Loc, "zero initial value for the first " | ||
| "enumerator in %0 can be disregarded") | ||
| << Enum; | ||
| cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); | ||
| return; | ||
| } | ||
| if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("sequential")) { | ||
| DiagnosticBuilder Diag = | ||
| diag(Enum->getBeginLoc(), | ||
| "sequential initial value in %0 can be ignored") | ||
| << Enum; | ||
| for (const EnumConstantDecl *ECD : llvm::drop_begin(Enum->enumerators())) | ||
| cleanInitialValue(Diag, ECD, *Result.SourceManager, getLangOpts()); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| } // namespace clang::tidy::readability |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| //===--- EnumInitialValueCheck.h - clang-tidy -------------------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::readability { | ||
|
|
||
| /// Enforces consistent style for enumerators' initialization, covering three | ||
| /// styles: none, first only, or all initialized explicitly. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/readability/enum-initial-value.html | ||
| class EnumInitialValueCheck : public ClangTidyCheck { | ||
| public: | ||
| EnumInitialValueCheck(StringRef Name, ClangTidyContext *Context); | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
|
|
||
| private: | ||
| const bool AllowExplicitZeroFirstInitialValue; | ||
| const bool AllowExplicitSequentialInitialValues; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::readability | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ENUMINITIALVALUECHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| //===--- BracesAroundStatement.cpp - clang-tidy -------- ------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// This file provides utilities to put braces around a statement. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "BracesAroundStatement.h" | ||
| #include "../utils/LexerUtils.h" | ||
| #include "LexerUtils.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/Basic/CharInfo.h" | ||
| #include "clang/Basic/LangOptions.h" | ||
| #include "clang/Lex/Lexer.h" | ||
|
|
||
| namespace clang::tidy::utils { | ||
|
|
||
| BraceInsertionHints::operator bool() const { return DiagnosticPos.isValid(); } | ||
|
|
||
| bool BraceInsertionHints::offersFixIts() const { | ||
| return OpeningBracePos.isValid() && ClosingBracePos.isValid(); | ||
| } | ||
|
|
||
| unsigned BraceInsertionHints::resultingCompoundLineExtent( | ||
| const SourceManager &SourceMgr) const { | ||
| return SourceMgr.getSpellingLineNumber(ClosingBracePos) - | ||
| SourceMgr.getSpellingLineNumber(OpeningBracePos); | ||
| } | ||
|
|
||
| FixItHint BraceInsertionHints::openingBraceFixIt() const { | ||
| return OpeningBracePos.isValid() | ||
| ? FixItHint::CreateInsertion(OpeningBracePos, " {") | ||
| : FixItHint(); | ||
| } | ||
|
|
||
| FixItHint BraceInsertionHints::closingBraceFixIt() const { | ||
| return ClosingBracePos.isValid() | ||
| ? FixItHint::CreateInsertion(ClosingBracePos, ClosingBrace) | ||
| : FixItHint(); | ||
| } | ||
|
|
||
| static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, | ||
| const LangOptions &LangOpts) { | ||
| Token Tok; | ||
| SourceLocation Beginning = Lexer::GetBeginningOfToken(Loc, SM, LangOpts); | ||
| const bool Invalid = Lexer::getRawToken(Beginning, Tok, SM, LangOpts); | ||
| assert(!Invalid && "Expected a valid token."); | ||
|
|
||
| if (Invalid) | ||
| return tok::NUM_TOKENS; | ||
|
|
||
| return Tok.getKind(); | ||
| } | ||
|
|
||
| static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, | ||
| const LangOptions &LangOpts) { | ||
| SourceLocation Loc = lexer::getUnifiedEndLoc(S, SM, LangOpts); | ||
| if (!Loc.isValid()) | ||
| return Loc; | ||
|
|
||
| // Start searching right after S. | ||
| Loc = Loc.getLocWithOffset(1); | ||
|
|
||
| for (;;) { | ||
| assert(Loc.isValid()); | ||
| while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) { | ||
| Loc = Loc.getLocWithOffset(1); | ||
| } | ||
|
|
||
| if (isVerticalWhitespace(*SM.getCharacterData(Loc))) { | ||
| // EOL, insert brace before. | ||
| break; | ||
| } | ||
| tok::TokenKind TokKind = getTokenKind(Loc, SM, LangOpts); | ||
| if (TokKind != tok::comment) { | ||
| // Non-comment token, insert brace before. | ||
| break; | ||
| } | ||
|
|
||
| SourceLocation TokEndLoc = Lexer::getLocForEndOfToken(Loc, 0, SM, LangOpts); | ||
| SourceRange TokRange(Loc, TokEndLoc); | ||
| StringRef Comment = Lexer::getSourceText( | ||
| CharSourceRange::getTokenRange(TokRange), SM, LangOpts); | ||
| if (Comment.starts_with("/*") && Comment.contains('\n')) { | ||
| // Multi-line block comment, insert brace before. | ||
| break; | ||
| } | ||
| // else: Trailing comment, insert brace after the newline. | ||
|
|
||
| // Fast-forward current token. | ||
| Loc = TokEndLoc; | ||
| } | ||
| return Loc; | ||
| } | ||
|
|
||
| BraceInsertionHints getBraceInsertionsHints(const Stmt *const S, | ||
| const LangOptions &LangOpts, | ||
| const SourceManager &SM, | ||
| SourceLocation StartLoc, | ||
| SourceLocation EndLocHint) { | ||
| // 1) If there's a corresponding "else" or "while", the check inserts "} " | ||
| // right before that token. | ||
| // 2) If there's a multi-line block comment starting on the same line after | ||
| // the location we're inserting the closing brace at, or there's a non-comment | ||
| // token, the check inserts "\n}" right before that token. | ||
| // 3) Otherwise the check finds the end of line (possibly after some block or | ||
| // line comments) and inserts "\n}" right before that EOL. | ||
| if (!S || isa<CompoundStmt>(S)) { | ||
| // Already inside braces. | ||
| return {}; | ||
| } | ||
|
|
||
| // When TreeTransform, Stmt in constexpr IfStmt will be transform to NullStmt. | ||
| // This NullStmt can be detected according to beginning token. | ||
| const SourceLocation StmtBeginLoc = S->getBeginLoc(); | ||
| if (isa<NullStmt>(S) && StmtBeginLoc.isValid() && | ||
| getTokenKind(StmtBeginLoc, SM, LangOpts) == tok::l_brace) | ||
| return {}; | ||
|
|
||
| if (StartLoc.isInvalid()) | ||
| return {}; | ||
|
|
||
| // Convert StartLoc to file location, if it's on the same macro expansion | ||
| // level as the start of the statement. We also need file locations for | ||
| // Lexer::getLocForEndOfToken working properly. | ||
| StartLoc = Lexer::makeFileCharRange( | ||
| CharSourceRange::getCharRange(StartLoc, S->getBeginLoc()), SM, | ||
| LangOpts) | ||
| .getBegin(); | ||
| if (StartLoc.isInvalid()) | ||
| return {}; | ||
| StartLoc = Lexer::getLocForEndOfToken(StartLoc, 0, SM, LangOpts); | ||
|
|
||
| // StartLoc points at the location of the opening brace to be inserted. | ||
| SourceLocation EndLoc; | ||
| std::string ClosingInsertion; | ||
| if (EndLocHint.isValid()) { | ||
| EndLoc = EndLocHint; | ||
| ClosingInsertion = "} "; | ||
| } else { | ||
| EndLoc = findEndLocation(*S, SM, LangOpts); | ||
| ClosingInsertion = "\n}"; | ||
| } | ||
|
|
||
| assert(StartLoc.isValid()); | ||
|
|
||
| // Change only if StartLoc and EndLoc are on the same macro expansion level. | ||
| // This will also catch invalid EndLoc. | ||
| // Example: LLVM_DEBUG( for(...) do_something() ); | ||
| // In this case fix-it cannot be provided as the semicolon which is not | ||
| // visible here is part of the macro. Adding braces here would require adding | ||
| // another semicolon. | ||
| if (Lexer::makeFileCharRange( | ||
| CharSourceRange::getTokenRange(SourceRange( | ||
| SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))), | ||
| SM, LangOpts) | ||
| .isInvalid()) | ||
| return {StartLoc}; | ||
| return {StartLoc, EndLoc, ClosingInsertion}; | ||
| } | ||
|
|
||
| } // namespace clang::tidy::utils |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| //===--- BracesAroundStatement.h - clang-tidy ------- -----------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| /// | ||
| /// \file | ||
| /// This file provides utilities to put braces around a statement. | ||
| /// | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "clang/AST/Stmt.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "clang/Basic/SourceManager.h" | ||
|
|
||
| namespace clang::tidy::utils { | ||
|
|
||
| /// A provider of fix-it hints to insert opening and closing braces. An instance | ||
| /// of this type is the result of calling `getBraceInsertionsHints` below. | ||
| struct BraceInsertionHints { | ||
| /// The position of a potential diagnostic. It coincides with the position of | ||
| /// the opening brace to insert, but can also just be the place to show a | ||
| /// diagnostic in case braces cannot be inserted automatically. | ||
| SourceLocation DiagnosticPos; | ||
|
|
||
| /// Constructor for a no-hint. | ||
| BraceInsertionHints() = default; | ||
|
|
||
| /// Constructor for a valid hint that cannot insert braces automatically. | ||
| BraceInsertionHints(SourceLocation DiagnosticPos) | ||
| : DiagnosticPos(DiagnosticPos) {} | ||
|
|
||
| /// Constructor for a hint offering fix-its for brace insertion. Both | ||
| /// positions must be valid. | ||
| BraceInsertionHints(SourceLocation OpeningBracePos, | ||
| SourceLocation ClosingBracePos, std::string ClosingBrace) | ||
| : DiagnosticPos(OpeningBracePos), OpeningBracePos(OpeningBracePos), | ||
| ClosingBracePos(ClosingBracePos), ClosingBrace(ClosingBrace) { | ||
| assert(offersFixIts()); | ||
| } | ||
|
|
||
| /// Indicates whether the hint provides at least the position of a diagnostic. | ||
| operator bool() const; | ||
|
|
||
| /// Indicates whether the hint provides fix-its to insert braces. | ||
| bool offersFixIts() const; | ||
|
|
||
| /// The number of lines between the inserted opening brace and its closing | ||
| /// counterpart. | ||
| unsigned resultingCompoundLineExtent(const SourceManager &SourceMgr) const; | ||
|
|
||
| /// Fix-it to insert an opening brace. | ||
| FixItHint openingBraceFixIt() const; | ||
|
|
||
| /// Fix-it to insert a closing brace. | ||
| FixItHint closingBraceFixIt() const; | ||
|
|
||
| private: | ||
| SourceLocation OpeningBracePos; | ||
| SourceLocation ClosingBracePos; | ||
| std::string ClosingBrace; | ||
| }; | ||
|
|
||
| /// Create fix-it hints for braces that wrap the given statement when applied. | ||
| /// The algorithm computing them respects comment before and after the statement | ||
| /// and adds line breaks before the braces accordingly. | ||
| BraceInsertionHints | ||
| getBraceInsertionsHints(const Stmt *const S, const LangOptions &LangOpts, | ||
| const SourceManager &SM, SourceLocation StartLoc, | ||
| SourceLocation EndLocHint = SourceLocation()); | ||
|
|
||
| } // namespace clang::tidy::utils |