diff --git a/bolt/test/X86/encoding-validation.s b/bolt/test/X86/encoding-validation.s index 0c716abc4cc0cf4..c01361296489502 100644 --- a/bolt/test/X86/encoding-validation.s +++ b/bolt/test/X86/encoding-validation.s @@ -2,7 +2,7 @@ # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o # RUN: ld.lld %t.o -o %t.exe -q -# RUN: llvm-bolt %t.exe --relocs -o %t.out --check-encoding |& FileCheck %s +# RUN: llvm-bolt %t.exe --relocs -o %t.out --check-encoding 2>&1 | FileCheck %s .text .globl _start diff --git a/bolt/test/X86/gotpcrelx.s b/bolt/test/X86/gotpcrelx.s index 6dec125c6a72bdf..c5bc7ac7d0d4723 100644 --- a/bolt/test/X86/gotpcrelx.s +++ b/bolt/test/X86/gotpcrelx.s @@ -10,11 +10,11 @@ # RUN: ld.lld %t.o -o %t.pie.exe -q -pie # RUN: ld.lld %t.o -o %t.no-relax.exe -q --no-relax # RUN: llvm-bolt %t.exe --relocs -o %t.out --print-cfg --print-only=_start \ -# RUN: |& FileCheck --check-prefix=BOLT %s +# RUN: 2>&1 | FileCheck --check-prefix=BOLT %s # RUN: llvm-bolt %t.pie.exe -o %t.null --print-cfg --print-only=_start \ -# RUN: |& FileCheck --check-prefix=PIE-BOLT %s +# RUN: 2>&1 | FileCheck --check-prefix=PIE-BOLT %s # RUN: llvm-bolt %t.no-relax.exe -o %t.null --print-cfg --print-only=_start \ -# RUN: |& FileCheck --check-prefix=NO-RELAX-BOLT %s +# RUN: 2>&1 | FileCheck --check-prefix=NO-RELAX-BOLT %s # RUN: llvm-objdump -d --no-show-raw-insn --print-imm-hex \ # RUN: %t.out | FileCheck --check-prefix=DISASM %s diff --git a/bolt/test/X86/icf-jump-tables.test b/bolt/test/X86/icf-jump-tables.test index 41820008b01338b..57137b79bc317e1 100644 --- a/bolt/test/X86/icf-jump-tables.test +++ b/bolt/test/X86/icf-jump-tables.test @@ -4,7 +4,7 @@ # REQUIRES: system-linux # RUN: %clang %cflags -O1 -g %p/../Inputs/icf-jump-tables.c -o %t.exe -Wl,-q -# RUN: llvm-bolt %t.exe --icf -o %t.bolt |& FileCheck %s +# RUN: llvm-bolt %t.exe --icf -o %t.bolt 2>&1 | FileCheck %s ## Check that BOLT successfully folded a function with jump table: # CHECK: ICF folded {{.*}}. {{[^0]}} functions had jump tables. diff --git a/bolt/test/X86/indirect-goto-pie.test b/bolt/test/X86/indirect-goto-pie.test index 81cff9a32fbbddc..3311c1aec061c5e 100644 --- a/bolt/test/X86/indirect-goto-pie.test +++ b/bolt/test/X86/indirect-goto-pie.test @@ -6,7 +6,7 @@ REQUIRES: x86_64-linux RUN: %clang %S/Inputs/indirect_goto.c -o %t -fpic -pie -Wl,-q RUN: not llvm-bolt %t -o %t.bolt --relocs=1 --print-cfg --print-only=main \ -RUN: |& FileCheck %s +RUN: 2>&1 | FileCheck %s ## Check that processing works if main() is skipped. RUN: llvm-bolt %t -o %t.bolt --relocs=1 --skip-funcs=main diff --git a/bolt/test/X86/jump-table-func-entry.s b/bolt/test/X86/jump-table-func-entry.s index 77b444d520a1f10..b34e7142785d4a0 100644 --- a/bolt/test/X86/jump-table-func-entry.s +++ b/bolt/test/X86/jump-table-func-entry.s @@ -7,7 +7,7 @@ # RUN: %clang %cflags %t.o -o %t.exe -no-pie -Wl,-q # RUN: llvm-bolt %t.exe --print-normalized --print-only=foo -o %t.out \ -# RUN: |& FileCheck %s +# RUN: 2>&1 | FileCheck %s diff --git a/bolt/test/X86/keep-nops.s b/bolt/test/X86/keep-nops.s index 37da2ff07b9b798..ddd34ce61d620de 100644 --- a/bolt/test/X86/keep-nops.s +++ b/bolt/test/X86/keep-nops.s @@ -5,7 +5,7 @@ # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o # RUN: ld.lld %t.o -o %t.exe -q # RUN: llvm-bolt %t.exe -o %t.bolt.exe --keep-nops --relocs --print-finalized \ -# RUN: |& FileCheck --check-prefix=CHECK-BOLT %s +# RUN: 2>&1 | FileCheck --check-prefix=CHECK-BOLT %s # RUN: llvm-objdump -d %t.bolt.exe | FileCheck %s .text diff --git a/bolt/test/X86/linux-bug-table.s b/bolt/test/X86/linux-bug-table.s index 63f70a0b35d9fe5..07a4729ade73747 100644 --- a/bolt/test/X86/linux-bug-table.s +++ b/bolt/test/X86/linux-bug-table.s @@ -15,7 +15,7 @@ ## Verify bug entry bindings again after unreachable code elimination. # RUN: llvm-bolt %t.out -o %t.out.1 --print-only=_start --print-normalized \ -# RUN: |& FileCheck --check-prefix=CHECK-REOPT %s +# RUN: 2>&1 | FileCheck --check-prefix=CHECK-REOPT %s # CHECK: BOLT-INFO: Linux kernel binary detected # CHECK: BOLT-INFO: parsed 2 bug table entries diff --git a/bolt/test/X86/linux-orc.s b/bolt/test/X86/linux-orc.s index 5f2096278e92d6a..1b0e681b1dbf96e 100644 --- a/bolt/test/X86/linux-orc.s +++ b/bolt/test/X86/linux-orc.s @@ -9,7 +9,7 @@ ## Verify reading contents of ORC sections. -# RUN: llvm-bolt %t.exe --dump-orc -o /dev/null |& FileCheck %s \ +# RUN: llvm-bolt %t.exe --dump-orc -o /dev/null 2>&1 | FileCheck %s \ # RUN: --check-prefix=CHECK-ORC # CHECK-ORC: BOLT-INFO: ORC unwind information: @@ -27,19 +27,19 @@ ## Verify ORC bindings to instructions. # RUN: llvm-bolt %t.exe --print-normalized --dump-orc --print-orc -o %t.out \ -# RUN: --keep-nops=0 --bolt-info=0 |& FileCheck %s +# RUN: --keep-nops=0 --bolt-info=0 2>&1 | FileCheck %s ## Verify ORC bindings after rewrite. # RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized --print-orc \ -# RUN: |& FileCheck %s +# RUN: 2>&1 | FileCheck %s ## Verify ORC binding after rewrite when some of the functions are skipped. # RUN: llvm-bolt %t.exe -o %t.out --skip-funcs=bar --bolt-info=0 --keep-nops=0 # RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized --print-orc \ -# RUN: |& FileCheck %s +# RUN: 2>&1 | FileCheck %s # CHECK: BOLT-INFO: Linux kernel binary detected # CHECK: BOLT-INFO: parsed 9 ORC entries diff --git a/bolt/test/X86/linux-pci-fixup.s b/bolt/test/X86/linux-pci-fixup.s index a574ba84c4df11e..42504c108d339ce 100644 --- a/bolt/test/X86/linux-pci-fixup.s +++ b/bolt/test/X86/linux-pci-fixup.s @@ -3,7 +3,7 @@ # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o # RUN: %clang %cflags -nostdlib %t.o -o %t.exe \ # RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr,--no-pie -# RUN: llvm-bolt %t.exe --print-normalized -o %t.out |& FileCheck %s +# RUN: llvm-bolt %t.exe --print-normalized -o %t.out 2>&1 | FileCheck %s ## Check that BOLT correctly parses the Linux kernel .pci_fixup section and ## verify that PCI fixup hook in the middle of a function is detected. diff --git a/bolt/test/X86/linux-smp-locks.s b/bolt/test/X86/linux-smp-locks.s index 5f4410d14fc6b08..50d9e632b117205 100644 --- a/bolt/test/X86/linux-smp-locks.s +++ b/bolt/test/X86/linux-smp-locks.s @@ -7,11 +7,11 @@ # RUN: %clang %cflags -nostdlib %t.o -o %t.exe \ # RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr,--no-pie # RUN: llvm-bolt %t.exe --print-normalized --keep-nops=0 --bolt-info=0 -o %t.out \ -# RUN: |& FileCheck %s +# RUN: 2>&1 | FileCheck %s ## Check the output of BOLT with NOPs removed. -# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized |& FileCheck %s +# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized 2>&1 | FileCheck %s # CHECK: BOLT-INFO: Linux kernel binary detected # CHECK: BOLT-INFO: parsed 2 SMP lock entries diff --git a/bolt/test/X86/linux-static-calls.s b/bolt/test/X86/linux-static-calls.s index caf95e1c03227d2..ce90f4bb79c094e 100644 --- a/bolt/test/X86/linux-static-calls.s +++ b/bolt/test/X86/linux-static-calls.s @@ -9,11 +9,11 @@ ## Verify static calls bindings to instructions. # RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \ -# RUN: --bolt-info=0 |& FileCheck %s +# RUN: --bolt-info=0 2>&1 | FileCheck %s ## Verify the bindings again on the rewritten binary with nops removed. -# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized |& FileCheck %s +# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized 2>&1 | FileCheck %s # CHECK: BOLT-INFO: Linux kernel binary detected # CHECK: BOLT-INFO: parsed 2 static call entries diff --git a/bolt/test/X86/linux-static-keys.s b/bolt/test/X86/linux-static-keys.s index fb419e0f7627559..0bd17a375d88248 100644 --- a/bolt/test/X86/linux-static-keys.s +++ b/bolt/test/X86/linux-static-keys.s @@ -11,17 +11,17 @@ ## Verify static keys jump bindings to instructions. # RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \ -# RUN: --bolt-info=0 |& FileCheck %s +# RUN: --bolt-info=0 2>&1 | FileCheck %s ## Verify that profile is matched correctly. # RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \ -# RUN: --bolt-info=0 --data %t.fdata |& \ -# RUN: FileCheck --check-prefix=CHECK-FDATA %s +# RUN: --bolt-info=0 --data %t.fdata 2>&1 \ +# RUN: | FileCheck --check-prefix=CHECK-FDATA %s ## Verify the bindings again on the rewritten binary with nops removed. -# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized |& FileCheck %s +# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized 2>&1 | FileCheck %s # CHECK: BOLT-INFO: Linux kernel binary detected # CHECK: BOLT-INFO: parsed 2 static keys jump entries diff --git a/bolt/test/X86/pt_gnu_relro.s b/bolt/test/X86/pt_gnu_relro.s index d7cfad5f954be5e..ff897b96e090970 100644 --- a/bolt/test/X86/pt_gnu_relro.s +++ b/bolt/test/X86/pt_gnu_relro.s @@ -22,7 +22,7 @@ # READELF: 04 .got # RUN: llvm-bolt %t.exe --relocs -o %t.null -v=1 \ -# RUN: |& FileCheck --check-prefix=BOLT %s +# RUN: 2>&1 | FileCheck --check-prefix=BOLT %s # BOLT: BOLT-INFO: marking .got as GNU_RELRO .globl _start diff --git a/bolt/test/X86/unclaimed-jt-entries.s b/bolt/test/X86/unclaimed-jt-entries.s index 2d56167286c36bb..1102e4ae413e277 100644 --- a/bolt/test/X86/unclaimed-jt-entries.s +++ b/bolt/test/X86/unclaimed-jt-entries.s @@ -18,7 +18,7 @@ # RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o # RUN: %clang %cflags -no-pie %t.o -o %t.exe -Wl,-q -# RUN: llvm-bolt %t.exe -v=1 -o %t.out |& FileCheck %s +# 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 diff --git a/bolt/test/X86/vararg.test b/bolt/test/X86/vararg.test index 0b8668a842ed4d5..296c739c6e11383 100644 --- a/bolt/test/X86/vararg.test +++ b/bolt/test/X86/vararg.test @@ -5,7 +5,7 @@ REQUIRES: x86_64-linux RUN: %clangxx %cxxflags -no-pie %p/../Inputs/vararg.s -o %t -Wl,-q -RUN: llvm-bolt %t -o %t.null --print-cfg --print-only=.*printf.* |& FileCheck %s +RUN: llvm-bolt %t -o %t.null --print-cfg --print-only=.*printf.* 2>&1 | FileCheck %s CHECK: IsSimple : 0 CHECK: Entry Point diff --git a/bolt/test/runtime/X86/unclaimed-jt-entries.s b/bolt/test/runtime/X86/unclaimed-jt-entries.s index d0691d256ba0458..1725fb808efbff6 100644 --- a/bolt/test/runtime/X86/unclaimed-jt-entries.s +++ b/bolt/test/runtime/X86/unclaimed-jt-entries.s @@ -18,7 +18,7 @@ # 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 |& FileCheck %s +# RUN: llvm-bolt %t.exe -v=1 -o %t.out --sequential-disassembly 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 diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index aef22453035c30a..21ee417da6028a8 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -352,6 +352,7 @@ 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, @@ -418,9 +419,13 @@ genRecordMembersBlock(const llvm::SmallVector &Members, if (Access != "") Access = Access + " "; auto LIBody = std::make_unique(HTMLTag::TAG_LI); - LIBody->Children.emplace_back(std::make_unique(Access)); - LIBody->Children.emplace_back(genReference(M.Type, ParentInfoDir)); - LIBody->Children.emplace_back(std::make_unique(" " + M.Name)); + auto MemberDecl = std::make_unique(HTMLTag::TAG_DIV); + MemberDecl->Children.emplace_back(std::make_unique(Access)); + 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; diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test index c2c7548f5a9690c..2865ed4446e7ef8 100644 --- a/clang-tools-extra/test/clang-doc/basic-project.test +++ b/clang-tools-extra/test/clang-doc/basic-project.test @@ -91,8 +91,10 @@ // HTML-RECTANGLE: Shape // HTML-RECTANGLE:

// HTML-RECTANGLE:

Members

-// HTML-RECTANGLE:
  • private double width_
  • -// HTML-RECTANGLE:
  • private double height_
  • +// HTML-RECTANGLE:

    Width of the rectangle.

    +// HTML-RECTANGLE:
    private double width_
    +// HTML-RECTANGLE:

    Height of the rectangle.

    +// HTML-RECTANGLE:
    private double height_
    // HTML-RECTANGLE:

    Functions

    // HTML-RECTANGLE:

    Rectangle

    // HTML-RECTANGLE:

    public void Rectangle(double width, double height)

    @@ -112,7 +114,8 @@ // HTML-CIRCLE: Shape // HTML-CIRCLE:

    // HTML-CIRCLE:

    Members

    -// HTML-CIRCLE:
  • private double radius_
  • +// HTML-CIRCLE:

    Radius of the circle.

    +// HTML-CIRCLE:
    private double radius_
    // HTML-CIRCLE:

    Functions

    // HTML-CIRCLE:

    Circle

    // HTML-CIRCLE:

    public void Circle(double radius)

    diff --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp index e4a7340318b9341..bd031282b042abb 100644 --- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp @@ -197,7 +197,9 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {

    Members

      -
    • private int X
    • +
    • +
      private int X
      +

    Records

      diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 6796a619ba97f8e..39e1b0fcb09bbd5 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -217,6 +217,8 @@ Bug Fixes to C++ Support - Clang now preserves the unexpanded flag in a lambda transform used for pack expansion. (#GH56852), (#GH85667), (#GH99877). - Fixed a bug when diagnosing ambiguous explicit specializations of constrained member functions. +- Fixed an assertion failure when selecting a function from an overload set that includes a + specialization of a conversion function template. Bug Fixes to AST Handling ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 94cf9113cd43a47..58a820508da42b6 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -34,6 +34,7 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -1194,8 +1195,8 @@ class ASTContext : public RefCountedBase { llvm::DenseSet CUDADeviceVarODRUsedByHost; /// Keep track of CUDA/HIP external kernels or device variables ODR-used by - /// host code. - llvm::DenseSet CUDAExternalDeviceDeclODRUsedByHost; + /// host code. SetVector is used to maintain the order. + llvm::SetVector CUDAExternalDeviceDeclODRUsedByHost; /// Keep track of CUDA/HIP implicit host device functions used on device side /// in device compilation. diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index a024f9b2a9f8c0d..eb82e0159b56ede 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -166,15 +166,22 @@ def note_constexpr_heap_alloc_limit_exceeded : Note< def note_constexpr_this : Note< "%select{|implicit }0use of 'this' pointer is only allowed within the " "evaluation of a call to a 'constexpr' member function">; -def note_constexpr_lifetime_ended : Note< +def access_kind : TextSubstitution< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 %select{temporary|variable}1 whose " - "%plural{8:storage duration|:lifetime}0 has ended">; -def note_constexpr_access_uninit : Note< + "destruction of}0">; +def access_kind_subobject : TextSubstitution< "%select{read of|read of|assignment to|increment of|decrement of|" "member call on|dynamic_cast of|typeid applied to|" - "construction of subobject of|destruction of}0 " + "construction of subobject of|destruction of}0">; +def access_kind_volatile : TextSubstitution< + "%select{read of|read of|assignment to|increment of|decrement of|" + "||||}0">; +def note_constexpr_lifetime_ended : Note< + "%sub{access_kind}0 %select{temporary|variable}1 whose " + "%plural{8:storage duration|:lifetime}0 has ended">; +def note_constexpr_access_uninit : Note< + "%sub{access_kind_subobject}0 " "%select{object outside its lifetime|uninitialized object}1 " "is not allowed in a constant expression">; def note_constexpr_use_uninit_reference : Note< @@ -184,20 +191,16 @@ def note_constexpr_modify_const_type : Note< "modification of object of const-qualified type %0 is not allowed " "in a constant expression">; def note_constexpr_access_volatile_type : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "|||}0 " + "%sub{access_kind_volatile}0 " "volatile-qualified type %1 is not allowed in a constant expression">; def note_constexpr_access_volatile_obj : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "|||}0 " + "%sub{access_kind_volatile}0 " "volatile %select{temporary|object %2|member %2}1 is not allowed in " "a constant expression">; def note_constexpr_volatile_here : Note< "volatile %select{temporary created|object declared|member declared}0 here">; def note_constexpr_access_mutable : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "%sub{access_kind}0 " "mutable member %1 is not allowed in a constant expression">; def note_constexpr_ltor_non_const_int : Note< "read of non-const variable %0 is not allowed in a constant expression">; @@ -209,47 +212,28 @@ def note_constexpr_ltor_non_constexpr : Note< def note_constexpr_ltor_incomplete_type : Note< "read of incomplete type %0 is not allowed in a constant expression">; def note_constexpr_access_null : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " + "%sub{access_kind}0 " "dereferenced null pointer is not allowed in a constant expression">; def note_constexpr_access_past_end : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " - "dereferenced one-past-the-end pointer is not allowed " - "in a constant expression">; + "%sub{access_kind}0 dereferenced one-past-the-end pointer " + "is not allowed in a constant expression">; def note_constexpr_access_unsized_array : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " - "element of array without known bound " + "%sub{access_kind}0 element of array without known bound " "is not allowed in a constant expression">; def note_constexpr_access_inactive_union_member : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|" - "construction of subobject of|destruction of}0 " + "%sub{access_kind_subobject}0 " "member %1 of union with %select{active member %3|no active member}2 " "is not allowed in a constant expression">; def note_constexpr_union_member_change_during_init : Note< "assignment would change active union member during the initialization of " "a different member of the same union">; def note_constexpr_access_static_temporary : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|reconstruction of|" - "destruction of}0 temporary " - "is not allowed in a constant expression outside the expression that " - "created the temporary">; + "%sub{access_kind}0 temporary is not allowed in a constant expression " + "outside the expression that created the temporary">; def note_constexpr_access_unreadable_object : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " - "object '%1' whose value is not known">; + "%sub{access_kind}0 object '%1' whose value is not known">; def note_constexpr_access_deleted_object : Note< - "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|construction of|" - "destruction of}0 " - "heap allocated object that has been deleted">; + "%sub{access_kind}0 heap allocated object that has been deleted">; def note_constexpr_modify_global : Note< "a constant expression cannot modify an object that is visible outside " "that expression">; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index f8d50d12bb9351d..12aab09f285567f 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1260,9 +1260,6 @@ def warn_pragma_intrinsic_builtin : Warning< def warn_pragma_unused_expected_var : Warning< "expected '#pragma unused' argument to be a variable name">, InGroup; -// - #pragma mc_func -def err_pragma_mc_func_not_supported : - Error<"#pragma mc_func is not supported">; // - #pragma init_seg def warn_pragma_init_seg_unsupported_target : Warning< "'#pragma init_seg' is only supported when targeting a " diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index e196c3dc5cb3be8..0b38139bd279725 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -8114,13 +8114,6 @@ def source_date_epoch : Separate<["-"], "source-date-epoch">, } // let Visibility = [CC1Option] -defm err_pragma_mc_func_aix : BoolFOption<"err-pragma-mc-func-aix", - PreprocessorOpts<"ErrorOnPragmaMcfuncOnAIX">, DefaultFalse, - PosFlag, - NegFlag>; - //===----------------------------------------------------------------------===// // CUDA Options //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/Lex/PreprocessorOptions.h b/clang/include/clang/Lex/PreprocessorOptions.h index 3f7dd9db18ba7d1..c2e3d68333024a5 100644 --- a/clang/include/clang/Lex/PreprocessorOptions.h +++ b/clang/include/clang/Lex/PreprocessorOptions.h @@ -211,10 +211,6 @@ class PreprocessorOptions { /// If set, the UNIX timestamp specified by SOURCE_DATE_EPOCH. std::optional SourceDateEpoch; - /// If set, the preprocessor reports an error when processing #pragma mc_func - /// on AIX. - bool ErrorOnPragmaMcfuncOnAIX = false; - public: PreprocessorOptions() : PrecompiledPreambleBytes(0, false) {} @@ -252,7 +248,6 @@ class PreprocessorOptions { PrecompiledPreambleBytes.first = 0; PrecompiledPreambleBytes.second = false; RetainExcludedConditionalBlocks = false; - ErrorOnPragmaMcfuncOnAIX = false; } }; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 39c5f588167edeb..99a0b0200fa06fe 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -221,7 +221,6 @@ class Parser : public CodeCompletionHandler { std::unique_ptr MaxTokensHerePragmaHandler; std::unique_ptr MaxTokensTotalPragmaHandler; std::unique_ptr RISCVPragmaHandler; - std::unique_ptr MCFuncPragmaHandler; std::unique_ptr CommentSemaHandler; diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index 13390007fde33c8..4a50b4487b66544 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -126,13 +126,17 @@ static bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, return true; assert(Ptr.inUnion()); + assert(Ptr.isField() && Ptr.getField()); Pointer U = Ptr.getBase(); Pointer C = Ptr; while (!U.isRoot() && U.inUnion() && !U.isActive()) { - C = U; + if (U.getField()) + C = U; U = U.getBase(); } + assert(C.isField()); + // Get the inactive field descriptor. const FieldDecl *InactiveField = C.getField(); assert(InactiveField); diff --git a/clang/lib/AST/Interp/InterpBuiltin.cpp b/clang/lib/AST/Interp/InterpBuiltin.cpp index 1841a2a4714d89d..c3370e2e5286e07 100644 --- a/clang/lib/AST/Interp/InterpBuiltin.cpp +++ b/clang/lib/AST/Interp/InterpBuiltin.cpp @@ -1635,7 +1635,58 @@ bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC, return true; } -bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest) { +static bool copyComposite(InterpState &S, CodePtr OpPC, const Pointer &Src, + Pointer &Dest, bool Activate); +static bool copyRecord(InterpState &S, CodePtr OpPC, const Pointer &Src, + Pointer &Dest, bool Activate = false) { + [[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc(); + const Descriptor *DestDesc = Dest.getFieldDesc(); + + auto copyField = [&](const Record::Field &F, bool Activate) -> bool { + Pointer DestField = Dest.atField(F.Offset); + if (std::optional FT = S.Ctx.classify(F.Decl->getType())) { + TYPE_SWITCH(*FT, { + DestField.deref() = Src.atField(F.Offset).deref(); + if (Src.atField(F.Offset).isInitialized()) + DestField.initialize(); + if (Activate) + DestField.activate(); + }); + return true; + } + // Composite field. + return copyComposite(S, OpPC, Src.atField(F.Offset), DestField, Activate); + }; + + assert(SrcDesc->isRecord()); + assert(SrcDesc->ElemRecord == DestDesc->ElemRecord); + const Record *R = DestDesc->ElemRecord; + for (const Record::Field &F : R->fields()) { + if (R->isUnion()) { + // For unions, only copy the active field. + const Pointer &SrcField = Src.atField(F.Offset); + if (SrcField.isActive()) { + if (!copyField(F, /*Activate=*/true)) + return false; + } + } else { + if (!copyField(F, Activate)) + return false; + } + } + + for (const Record::Base &B : R->bases()) { + Pointer DestBase = Dest.atField(B.Offset); + if (!copyRecord(S, OpPC, Src.atField(B.Offset), DestBase, Activate)) + return false; + } + + Dest.initialize(); + return true; +} + +static bool copyComposite(InterpState &S, CodePtr OpPC, const Pointer &Src, + Pointer &Dest, bool Activate = false) { assert(Src.isLive() && Dest.isLive()); [[maybe_unused]] const Descriptor *SrcDesc = Src.getFieldDesc(); @@ -1657,44 +1708,14 @@ bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest) { return true; } - if (DestDesc->isRecord()) { - auto copyField = [&](const Record::Field &F, bool Activate) -> bool { - Pointer DestField = Dest.atField(F.Offset); - if (std::optional FT = S.Ctx.classify(F.Decl->getType())) { - TYPE_SWITCH(*FT, { - DestField.deref() = Src.atField(F.Offset).deref(); - DestField.initialize(); - if (Activate) - DestField.activate(); - }); - return true; - } - return Invalid(S, OpPC); - }; - - assert(SrcDesc->isRecord()); - assert(SrcDesc->ElemRecord == DestDesc->ElemRecord); - const Record *R = DestDesc->ElemRecord; - for (const Record::Field &F : R->fields()) { - if (R->isUnion()) { - // For unions, only copy the active field. - const Pointer &SrcField = Src.atField(F.Offset); - if (SrcField.isActive()) { - if (!copyField(F, /*Activate=*/true)) - return false; - } - } else { - if (!copyField(F, /*Activate=*/false)) - return false; - } - } - return true; - } - - // FIXME: Composite types. - + if (DestDesc->isRecord()) + return copyRecord(S, OpPC, Src, Dest, Activate); return Invalid(S, OpPC); } +bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest) { + return copyComposite(S, OpPC, Src, Dest); +} + } // namespace interp } // namespace clang diff --git a/clang/lib/Driver/ToolChains/AIX.cpp b/clang/lib/Driver/ToolChains/AIX.cpp index b2885b7776d1321..c2de7328c25c5d9 100644 --- a/clang/lib/Driver/ToolChains/AIX.cpp +++ b/clang/lib/Driver/ToolChains/AIX.cpp @@ -560,12 +560,6 @@ void AIX::addClangTargetOptions( if (!Args.getLastArgNoClaim(options::OPT_fsized_deallocation, options::OPT_fno_sized_deallocation)) CC1Args.push_back("-fno-sized-deallocation"); - - if (Args.hasFlag(options::OPT_ferr_pragma_mc_func_aix, - options::OPT_fno_err_pragma_mc_func_aix, false)) - CC1Args.push_back("-ferr-pragma-mc-func-aix"); - else - CC1Args.push_back("-fno-err-pragma-mc-func-aix"); } void AIX::addProfileRTLibs(const llvm::opt::ArgList &Args, diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp index aef4ddb75881644..cc6f18b5b319f95 100644 --- a/clang/lib/Parse/ParsePragma.cpp +++ b/clang/lib/Parse/ParsePragma.cpp @@ -14,7 +14,6 @@ #include "clang/Basic/PragmaKinds.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" -#include "clang/Lex/PreprocessorOptions.h" #include "clang/Lex/Token.h" #include "clang/Parse/LoopHint.h" #include "clang/Parse/ParseDiagnostic.h" @@ -412,19 +411,6 @@ struct PragmaRISCVHandler : public PragmaHandler { Sema &Actions; }; -struct PragmaMCFuncHandler : public PragmaHandler { - PragmaMCFuncHandler(bool ReportError) - : PragmaHandler("mc_func"), ReportError(ReportError) {} - void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, - Token &Tok) override { - if (ReportError) - PP.Diag(Tok, diag::err_pragma_mc_func_not_supported); - } - -private: - bool ReportError = false; -}; - void markAsReinjectedForRelexing(llvm::MutableArrayRef Toks) { for (auto &T : Toks) T.setFlag(clang::Token::IsReinjected); @@ -582,12 +568,6 @@ void Parser::initializePragmaHandlers() { RISCVPragmaHandler = std::make_unique(Actions); PP.AddPragmaHandler("clang", RISCVPragmaHandler.get()); } - - if (getTargetInfo().getTriple().isOSAIX()) { - MCFuncPragmaHandler = std::make_unique( - PP.getPreprocessorOpts().ErrorOnPragmaMcfuncOnAIX); - PP.AddPragmaHandler(MCFuncPragmaHandler.get()); - } } void Parser::resetPragmaHandlers() { @@ -722,11 +702,6 @@ void Parser::resetPragmaHandlers() { PP.RemovePragmaHandler("clang", RISCVPragmaHandler.get()); RISCVPragmaHandler.reset(); } - - if (getTargetInfo().getTriple().isOSAIX()) { - PP.RemovePragmaHandler(MCFuncPragmaHandler.get()); - MCFuncPragmaHandler.reset(); - } } /// Handle the annotation token produced for #pragma unused(...) diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index e9705ec43d86cc6..ec951d5ac06dbc6 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -5805,12 +5805,19 @@ FunctionDecl *Sema::getMoreConstrainedFunction(FunctionDecl *FD1, FunctionDecl *FD2) { assert(!FD1->getDescribedTemplate() && !FD2->getDescribedTemplate() && "not for function templates"); + assert(!FD1->isFunctionTemplateSpecialization() || + isa(FD1)); + assert(!FD2->isFunctionTemplateSpecialization() || + isa(FD2)); + FunctionDecl *F1 = FD1; - if (FunctionDecl *MF = FD1->getInstantiatedFromMemberFunction()) - F1 = MF; + if (FunctionDecl *P = FD1->getTemplateInstantiationPattern(false)) + F1 = P; + FunctionDecl *F2 = FD2; - if (FunctionDecl *MF = FD2->getInstantiatedFromMemberFunction()) - F2 = MF; + if (FunctionDecl *P = FD2->getTemplateInstantiationPattern(false)) + F2 = P; + llvm::SmallVector AC1, AC2; F1->getAssociatedConstraints(AC1); F2->getAssociatedConstraints(AC2); diff --git a/clang/test/AST/Interp/unions.cpp b/clang/test/AST/Interp/unions.cpp index 996d29e143fe2c8..35b4a520baa2695 100644 --- a/clang/test/AST/Interp/unions.cpp +++ b/clang/test/AST/Interp/unions.cpp @@ -361,7 +361,7 @@ namespace CopyCtor { namespace UnionInBase { struct Base { - int y; + int y; // both-note {{subobject declared here}} }; struct A : Base { int x; @@ -380,5 +380,29 @@ namespace UnionInBase { } static_assert(read_wrong_member_indirect() == 1); // both-error {{not an integral constant expression}} \ // both-note {{in call to}} + constexpr int read_uninitialized() { + B b = {.b = 1}; + int *p = &b.a.y; + b.a.x = 1; + return *p; // both-note {{read of uninitialized object}} + } + static_assert(read_uninitialized() == 0); // both-error {{constant}} \ + // both-note {{in call}} + constexpr int write_uninitialized() { + B b = {.b = 1}; + int *p = &b.a.y; + b.a.x = 1; + *p = 1; + return *p; + } + + constexpr B return_uninit() { + B b = {.b = 1}; + b.a.x = 2; + return b; + } + constexpr B uninit = return_uninit(); // both-error {{constant expression}} \ + // both-note {{subobject 'y' is not initialized}} + static_assert(return_uninit().a.x == 2); } #endif diff --git a/clang/test/CodeGenCUDA/host-used-extern-determinism.cu b/clang/test/CodeGenCUDA/host-used-extern-determinism.cu new file mode 100644 index 000000000000000..1e52887b894b17a --- /dev/null +++ b/clang/test/CodeGenCUDA/host-used-extern-determinism.cu @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -fcuda-is-device -x hip %s \ +// RUN: -fgpu-rdc -std=c++11 -emit-llvm -o - -target-cpu gfx906 | FileCheck %s + +#include "Inputs/cuda.h" + +// CHECK-LABEL: @__clang_gpu_used_external = internal {{.*}}global +// References to the kernels must be in order of appearance. +// CHECK-SAME: [ptr @_Z6kernelILi3EEvPi, ptr @_Z6kernelILi1EEvPi, ptr @_Z6kernelILi2EEvPi, ptr @_Z6kernelILi0EEvPi] + +template +__global__ void kernel(int* out) { *out = N; } + +void host(int n) { + void * k; + switch (n) { + case 3: k = (void*)&kernel<3>; break; + case 1: k = (void*)&kernel<1>; break; + case 2: k = (void*)&kernel<2>; break; + case 0: k = (void*)&kernel<0>; break; + } +} diff --git a/clang/test/Preprocessor/pragma_mc_func.c b/clang/test/Preprocessor/pragma_mc_func.c deleted file mode 100644 index f0d3e49e5dddcaf..000000000000000 --- a/clang/test/Preprocessor/pragma_mc_func.c +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: not %clang --target=powerpc64-ibm-aix -ferr-pragma-mc-func-aix -fsyntax-only \ -// RUN: %s 2>&1 | FileCheck %s -#pragma mc_func asm_barrier {"60000000"} - -// CHECK: error: #pragma mc_func is not supported - -// Cases where no errors occur. -// RUN: %clang --target=powerpc64-ibm-aix -fno-err-pragma-mc-func-aix -fsyntax-only %s -// RUN: %clang --target=powerpc64-ibm-aix -ferr-pragma-mc-func-aix -fsyntax-only \ -// RUN: -fno-err-pragma-mc-func-aix %s -// RUN: %clang --target=powerpc64-ibm-aix -fsyntax-only %s -// RUN: %clang --target=powerpc64-ibm-aix -Werror=unknown-pragmas \ -// RUN: -fno-err-pragma-mc-func-aix -fsyntax-only %s - -// Cases where we have errors or warnings. -// RUN: not %clang --target=powerpc64le-unknown-linux-gnu \ -// RUN: -Werror=unknown-pragmas -fno-err-pragma-mc-func-aix -fsyntax-only %s 2>&1 | \ -// RUN: FileCheck --check-prefix=UNUSED %s -// RUN: %clang --target=powerpc64le-unknown-linux-gnu \ -// RUN: -fno-err-pragma-mc-func-aix -fsyntax-only %s 2>&1 | \ -// RUN: FileCheck --check-prefix=UNUSED %s - -// UNUSED: clang: warning: argument unused during compilation: '-fno-err-pragma-mc-func-aix' [-Wunused-command-line-argument] diff --git a/clang/test/SemaCXX/PR98671.cpp b/clang/test/SemaCXX/PR98671.cpp new file mode 100644 index 000000000000000..f50518673588520 --- /dev/null +++ b/clang/test/SemaCXX/PR98671.cpp @@ -0,0 +1,28 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only %s -verify + +struct S1 { + operator int(); + + template + operator T(); +}; + + +// Ensure that no assertion is raised when overload resolution fails while +// choosing between an operator function template and an operator function. +constexpr auto r = &S1::operator int; +// expected-error@-1 {{initializer of type ''}} + + +template +struct S2 { + template + S2(U={}) requires (sizeof(T) > 0) {} + // expected-note@-1 {{candidate constructor}} + + template + S2(U={}) requires (true) {} + // expected-note@-1 {{candidate constructor}} +}; + +S2 s; // expected-error {{call to constructor of 'S2' is ambiguous}} diff --git a/compiler-rt/lib/nsan/CMakeLists.txt b/compiler-rt/lib/nsan/CMakeLists.txt index fa9f02abdf0801c..2846f0292307b3a 100644 --- a/compiler-rt/lib/nsan/CMakeLists.txt +++ b/compiler-rt/lib/nsan/CMakeLists.txt @@ -4,9 +4,11 @@ include_directories(..) set(NSAN_SOURCES nsan.cpp + nsan_allocator.cpp nsan_flags.cpp nsan_interceptors.cpp nsan_malloc_linux.cpp + nsan_new_delete.cpp nsan_stats.cpp nsan_suppressions.cpp nsan_thread.cpp diff --git a/compiler-rt/lib/nsan/nsan.cpp b/compiler-rt/lib/nsan/nsan.cpp index 7d10681a1bc9178..bfa55c317cfe79e 100644 --- a/compiler-rt/lib/nsan/nsan.cpp +++ b/compiler-rt/lib/nsan/nsan.cpp @@ -807,6 +807,7 @@ extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __nsan_init() { if (nsan_initialized) return; nsan_init_is_running = true; + SanitizerToolName = "NumericalStabilitySanitizer"; InitializeFlags(); InitializeSuppressions(); @@ -814,11 +815,12 @@ extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __nsan_init() { DisableCoreDumperIfNecessary(); - if (!MmapFixedNoReserve(TypesAddr(), UnusedAddr() - TypesAddr())) + if (!MmapFixedNoReserve(TypesAddr(), AllocatorAddr() - TypesAddr())) Die(); InitializeInterceptors(); NsanTSDInit(NsanTSDDtor); + NsanAllocatorInit(); NsanThread *main_thread = NsanThread::Create(nullptr, nullptr); SetCurrentThread(main_thread); diff --git a/compiler-rt/lib/nsan/nsan.h b/compiler-rt/lib/nsan/nsan.h index 4e88ef4c00974d5..08dd02746be65a0 100644 --- a/compiler-rt/lib/nsan/nsan.h +++ b/compiler-rt/lib/nsan/nsan.h @@ -51,6 +51,14 @@ SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE const char * __nsan_default_options(); } +// Unwind the stack for fatal error, as the parameter `stack` is +// empty without origins. +#define GET_FATAL_STACK_TRACE_IF_EMPTY(STACK) \ + if (nsan_initialized && (STACK)->size == 0) { \ + (STACK)->Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, \ + common_flags()->fast_unwind_on_fatal); \ + } + namespace __nsan { extern bool nsan_initialized; diff --git a/compiler-rt/lib/nsan/nsan_allocator.cpp b/compiler-rt/lib/nsan/nsan_allocator.cpp new file mode 100644 index 000000000000000..19004ad7dc8dbe7 --- /dev/null +++ b/compiler-rt/lib/nsan/nsan_allocator.cpp @@ -0,0 +1,340 @@ +//===- nsan_allocator.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 +// +//===----------------------------------------------------------------------===// +// +// NumericalStabilitySanitizer allocator. +// +//===----------------------------------------------------------------------===// + +#include "nsan_allocator.h" +#include "interception/interception.h" +#include "nsan.h" +#include "nsan_flags.h" +#include "nsan_platform.h" +#include "nsan_thread.h" +#include "sanitizer_common/sanitizer_allocator.h" +#include "sanitizer_common/sanitizer_allocator_checks.h" +#include "sanitizer_common/sanitizer_allocator_interface.h" +#include "sanitizer_common/sanitizer_allocator_report.h" +#include "sanitizer_common/sanitizer_common.h" +#include "sanitizer_common/sanitizer_errno.h" + +using namespace __nsan; + +DECLARE_REAL(void *, memcpy, void *dest, const void *src, uptr n) +DECLARE_REAL(void *, memset, void *dest, int c, uptr n) + +namespace { +struct Metadata { + uptr requested_size; +}; + +struct NsanMapUnmapCallback { + void OnMap(uptr p, uptr size) const {} + void OnMapSecondary(uptr p, uptr size, uptr user_begin, + uptr user_size) const {} + void OnUnmap(uptr p, uptr size) const {} +}; + +const uptr kMaxAllowedMallocSize = 1ULL << 40; + +// Allocator64 parameters. Deliberately using a short name. +struct AP64 { + static const uptr kSpaceBeg = Mapping::kHeapMemBeg; + static const uptr kSpaceSize = 0x40000000000; // 4T. + static const uptr kMetadataSize = sizeof(Metadata); + using SizeClassMap = DefaultSizeClassMap; + using MapUnmapCallback = NsanMapUnmapCallback; + static const uptr kFlags = 0; + using AddressSpaceView = LocalAddressSpaceView; +}; +} // namespace + +using PrimaryAllocator = SizeClassAllocator64; +using Allocator = CombinedAllocator; +using AllocatorCache = Allocator::AllocatorCache; + +static Allocator allocator; +static AllocatorCache fallback_allocator_cache; +static StaticSpinMutex fallback_mutex; + +static uptr max_malloc_size; + +void __nsan::NsanAllocatorInit() { + SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null); + allocator.Init(common_flags()->allocator_release_to_os_interval_ms); + if (common_flags()->max_allocation_size_mb) + max_malloc_size = Min(common_flags()->max_allocation_size_mb << 20, + kMaxAllowedMallocSize); + else + max_malloc_size = kMaxAllowedMallocSize; +} + +static AllocatorCache *GetAllocatorCache(NsanThreadLocalMallocStorage *ms) { + CHECK_LE(sizeof(AllocatorCache), sizeof(ms->allocator_cache)); + return reinterpret_cast(ms->allocator_cache); +} + +void NsanThreadLocalMallocStorage::Init() { + allocator.InitCache(GetAllocatorCache(this)); +} + +void NsanThreadLocalMallocStorage::CommitBack() { + allocator.SwallowCache(GetAllocatorCache(this)); + allocator.DestroyCache(GetAllocatorCache(this)); +} + +static void *NsanAllocate(uptr size, uptr alignment, bool zero) { + if (UNLIKELY(size > max_malloc_size)) { + if (AllocatorMayReturnNull()) { + Report("WARNING: NumericalStabilitySanitizer failed to allocate 0x%zx " + "bytes\n", + size); + return nullptr; + } + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportAllocationSizeTooBig(size, max_malloc_size, &stack); + } + if (UNLIKELY(IsRssLimitExceeded())) { + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportRssLimitExceeded(&stack); + } + + void *allocated; + if (NsanThread *t = GetCurrentThread()) { + AllocatorCache *cache = GetAllocatorCache(&t->malloc_storage()); + allocated = allocator.Allocate(cache, size, alignment); + } else { + SpinMutexLock l(&fallback_mutex); + AllocatorCache *cache = &fallback_allocator_cache; + allocated = allocator.Allocate(cache, size, alignment); + } + if (UNLIKELY(!allocated)) { + SetAllocatorOutOfMemory(); + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportOutOfMemory(size, &stack); + } + auto *meta = reinterpret_cast(allocator.GetMetaData(allocated)); + meta->requested_size = size; + if (zero && allocator.FromPrimary(allocated)) + REAL(memset)(allocated, 0, size); + __nsan_set_value_unknown(allocated, size); + RunMallocHooks(allocated, size); + return allocated; +} + +void __nsan::NsanDeallocate(void *p) { + DCHECK(p); + RunFreeHooks(p); + auto *meta = reinterpret_cast(allocator.GetMetaData(p)); + uptr size = meta->requested_size; + meta->requested_size = 0; + if (flags().poison_in_free) + __nsan_set_value_unknown(p, size); + if (NsanThread *t = GetCurrentThread()) { + AllocatorCache *cache = GetAllocatorCache(&t->malloc_storage()); + allocator.Deallocate(cache, p); + } else { + // In a just created thread, glibc's _dl_deallocate_tls might reach here + // before nsan_current_thread is set. + SpinMutexLock l(&fallback_mutex); + AllocatorCache *cache = &fallback_allocator_cache; + allocator.Deallocate(cache, p); + } +} + +static void *NsanReallocate(void *ptr, uptr new_size, uptr alignment) { + Metadata *meta = reinterpret_cast(allocator.GetMetaData(ptr)); + uptr old_size = meta->requested_size; + uptr actually_allocated_size = allocator.GetActuallyAllocatedSize(ptr); + if (new_size <= actually_allocated_size) { + // We are not reallocating here. + meta->requested_size = new_size; + if (new_size > old_size) + __nsan_set_value_unknown((u8 *)ptr + old_size, new_size - old_size); + return ptr; + } + void *new_p = NsanAllocate(new_size, alignment, false); + if (new_p) { + uptr memcpy_size = Min(new_size, old_size); + REAL(memcpy)(new_p, ptr, memcpy_size); + __nsan_copy_values(new_p, ptr, memcpy_size); + NsanDeallocate(ptr); + } + return new_p; +} + +static void *NsanCalloc(uptr nmemb, uptr size) { + if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportCallocOverflow(nmemb, size, &stack); + } + return NsanAllocate(nmemb * size, sizeof(u64), true); +} + +static const void *AllocationBegin(const void *p) { + if (!p) + return nullptr; + void *beg = allocator.GetBlockBegin(p); + if (!beg) + return nullptr; + auto *b = reinterpret_cast(allocator.GetMetaData(beg)); + if (!b) + return nullptr; + if (b->requested_size == 0) + return nullptr; + + return beg; +} + +static uptr AllocationSizeFast(const void *p) { + return reinterpret_cast(allocator.GetMetaData(p))->requested_size; +} + +static uptr AllocationSize(const void *p) { + if (!p) + return 0; + if (allocator.GetBlockBegin(p) != p) + return 0; + return AllocationSizeFast(p); +} + +void *__nsan::nsan_malloc(uptr size) { + return SetErrnoOnNull(NsanAllocate(size, sizeof(u64), false)); +} + +void *__nsan::nsan_calloc(uptr nmemb, uptr size) { + return SetErrnoOnNull(NsanCalloc(nmemb, size)); +} + +void *__nsan::nsan_realloc(void *ptr, uptr size) { + if (!ptr) + return SetErrnoOnNull(NsanAllocate(size, sizeof(u64), false)); + if (size == 0) { + NsanDeallocate(ptr); + return nullptr; + } + return SetErrnoOnNull(NsanReallocate(ptr, size, sizeof(u64))); +} + +void *__nsan::nsan_reallocarray(void *ptr, uptr nmemb, uptr size) { + if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) { + errno = errno_ENOMEM; + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportReallocArrayOverflow(nmemb, size, &stack); + } + return nsan_realloc(ptr, nmemb * size); +} + +void *__nsan::nsan_valloc(uptr size) { + return SetErrnoOnNull(NsanAllocate(size, GetPageSizeCached(), false)); +} + +void *__nsan::nsan_pvalloc(uptr size) { + uptr PageSize = GetPageSizeCached(); + if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) { + errno = errno_ENOMEM; + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportPvallocOverflow(size, &stack); + } + // pvalloc(0) should allocate one page. + size = size ? RoundUpTo(size, PageSize) : PageSize; + return SetErrnoOnNull(NsanAllocate(size, PageSize, false)); +} + +void *__nsan::nsan_aligned_alloc(uptr alignment, uptr size) { + if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) { + errno = errno_EINVAL; + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportInvalidAlignedAllocAlignment(size, alignment, &stack); + } + return SetErrnoOnNull(NsanAllocate(size, alignment, false)); +} + +void *__nsan::nsan_memalign(uptr alignment, uptr size) { + if (UNLIKELY(!IsPowerOfTwo(alignment))) { + errno = errno_EINVAL; + if (AllocatorMayReturnNull()) + return nullptr; + BufferedStackTrace stack; + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); + ReportInvalidAllocationAlignment(alignment, &stack); + } + return SetErrnoOnNull(NsanAllocate(size, alignment, false)); +} + +int __nsan::nsan_posix_memalign(void **memptr, uptr alignment, uptr size) { + if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) { + if (AllocatorMayReturnNull()) + return errno_EINVAL; + BufferedStackTrace stack; + ReportInvalidPosixMemalignAlignment(alignment, &stack); + } + void *ptr = NsanAllocate(size, alignment, false); + if (UNLIKELY(!ptr)) + // OOM error is already taken care of by NsanAllocate. + return errno_ENOMEM; + DCHECK(IsAligned((uptr)ptr, alignment)); + *memptr = ptr; + return 0; +} + +extern "C" { +uptr __sanitizer_get_current_allocated_bytes() { + uptr stats[AllocatorStatCount]; + allocator.GetStats(stats); + return stats[AllocatorStatAllocated]; +} + +uptr __sanitizer_get_heap_size() { + uptr stats[AllocatorStatCount]; + allocator.GetStats(stats); + return stats[AllocatorStatMapped]; +} + +uptr __sanitizer_get_free_bytes() { return 1; } + +uptr __sanitizer_get_unmapped_bytes() { return 1; } + +uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; } + +int __sanitizer_get_ownership(const void *p) { return AllocationSize(p) != 0; } + +const void *__sanitizer_get_allocated_begin(const void *p) { + return AllocationBegin(p); +} + +uptr __sanitizer_get_allocated_size(const void *p) { return AllocationSize(p); } + +uptr __sanitizer_get_allocated_size_fast(const void *p) { + DCHECK_EQ(p, __sanitizer_get_allocated_begin(p)); + uptr ret = AllocationSizeFast(p); + DCHECK_EQ(ret, __sanitizer_get_allocated_size(p)); + return ret; +} + +void __sanitizer_purge_allocator() { allocator.ForceReleaseToOS(); } +} diff --git a/compiler-rt/lib/nsan/nsan_allocator.h b/compiler-rt/lib/nsan/nsan_allocator.h new file mode 100644 index 000000000000000..d41560493c1a74c --- /dev/null +++ b/compiler-rt/lib/nsan/nsan_allocator.h @@ -0,0 +1,41 @@ +//===-- nsan_allocator.h ----------------------------------------*- 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 NSAN_ALLOCATOR_H +#define NSAN_ALLOCATOR_H + +#include "sanitizer_common/sanitizer_common.h" + +namespace __nsan { + +struct NsanThreadLocalMallocStorage { + // Allocator cache contains atomic_uint64_t which must be 8-byte aligned. + alignas(8) uptr allocator_cache[96 * (512 * 8 + 16)]; // Opaque. + void Init(); + void CommitBack(); + +private: + // These objects are allocated via mmap() and are zero-initialized. + NsanThreadLocalMallocStorage() {} +}; + +void NsanAllocatorInit(); +void NsanDeallocate(void *ptr); + +void *nsan_malloc(uptr size); +void *nsan_calloc(uptr nmemb, uptr size); +void *nsan_realloc(void *ptr, uptr size); +void *nsan_reallocarray(void *ptr, uptr nmemb, uptr size); +void *nsan_valloc(uptr size); +void *nsan_pvalloc(uptr size); +void *nsan_aligned_alloc(uptr alignment, uptr size); +void *nsan_memalign(uptr alignment, uptr size); +int nsan_posix_memalign(void **memptr, uptr alignment, uptr size); + +} // namespace __nsan +#endif // NSAN_ALLOCATOR_H diff --git a/compiler-rt/lib/nsan/nsan_flags.inc b/compiler-rt/lib/nsan/nsan_flags.inc index 63c15475f6754bb..658cd5b3b01bf4d 100644 --- a/compiler-rt/lib/nsan/nsan_flags.inc +++ b/compiler-rt/lib/nsan/nsan_flags.inc @@ -46,4 +46,5 @@ NSAN_FLAG(bool, enable_loadtracking_stats, false, "If true, compute load tracking stats, i.e. for each load from " "memory, the number of times nsan resumed from the original value " "due to invalid or unknown types.") +NSAN_FLAG(bool, poison_in_free, true, "") NSAN_FLAG(bool, print_stats_on_exit, false, "If true, print stats on exit.") diff --git a/compiler-rt/lib/nsan/nsan_malloc_linux.cpp b/compiler-rt/lib/nsan/nsan_malloc_linux.cpp index 02f52e7be07facb..c97591e4ac1593e 100644 --- a/compiler-rt/lib/nsan/nsan_malloc_linux.cpp +++ b/compiler-rt/lib/nsan/nsan_malloc_linux.cpp @@ -12,14 +12,16 @@ #include "interception/interception.h" #include "nsan.h" +#include "nsan_allocator.h" #include "sanitizer_common/sanitizer_allocator_dlsym.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_platform.h" #include "sanitizer_common/sanitizer_platform_interceptors.h" +#include "sanitizer_common/sanitizer_stacktrace.h" #if !SANITIZER_APPLE && !SANITIZER_WINDOWS using namespace __sanitizer; -using __nsan::nsan_initialized; +using namespace __nsan; namespace { struct DlsymAlloc : public DlSymAllocator { @@ -28,78 +30,53 @@ struct DlsymAlloc : public DlSymAllocator { } // namespace INTERCEPTOR(void *, aligned_alloc, uptr align, uptr size) { - void *res = REAL(aligned_alloc)(align, size); - if (res) - __nsan_set_value_unknown(static_cast(res), size); - return res; + return nsan_aligned_alloc(align, size); } INTERCEPTOR(void *, calloc, uptr nmemb, uptr size) { if (DlsymAlloc::Use()) return DlsymAlloc::Callocate(nmemb, size); - - void *res = REAL(calloc)(nmemb, size); - if (res) - __nsan_set_value_unknown(static_cast(res), nmemb * size); - return res; + return nsan_calloc(nmemb, size); } INTERCEPTOR(void, free, void *ptr) { + if (UNLIKELY(!ptr)) + return; if (DlsymAlloc::PointerIsMine(ptr)) return DlsymAlloc::Free(ptr); - REAL(free)(ptr); + NsanDeallocate(ptr); } INTERCEPTOR(void *, malloc, uptr size) { if (DlsymAlloc::Use()) return DlsymAlloc::Allocate(size); - void *res = REAL(malloc)(size); - if (res) - __nsan_set_value_unknown(static_cast(res), size); - return res; + return nsan_malloc(size); } INTERCEPTOR(void *, realloc, void *ptr, uptr size) { if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(ptr)) return DlsymAlloc::Realloc(ptr, size); - void *res = REAL(realloc)(ptr, size); - // TODO: We might want to copy the types from the original allocation - // (although that would require that we know its size). - if (res) - __nsan_set_value_unknown(static_cast(res), size); - return res; + return nsan_realloc(ptr, size); } #if SANITIZER_INTERCEPT_REALLOCARRAY INTERCEPTOR(void *, reallocarray, void *ptr, uptr nmemb, uptr size) { - void *res = REAL(reallocarray)(ptr, nmemb, size); - if (res) - __nsan_set_value_unknown(static_cast(res), nmemb * size); - return res; + return nsan_reallocarray(ptr, nmemb, size); } #endif // SANITIZER_INTERCEPT_REALLOCARRAY INTERCEPTOR(int, posix_memalign, void **memptr, uptr align, uptr size) { - int res = REAL(posix_memalign)(memptr, align, size); - if (res == 0 && *memptr) - __nsan_set_value_unknown(static_cast(*memptr), size); - return res; + return nsan_posix_memalign(memptr, align, size); } // Deprecated allocation functions (memalign, etc). #if SANITIZER_INTERCEPT_MEMALIGN INTERCEPTOR(void *, memalign, uptr align, uptr size) { - void *const res = REAL(memalign)(align, size); - if (res) - __nsan_set_value_unknown(static_cast(res), size); - return res; + return nsan_memalign(align, size); } INTERCEPTOR(void *, __libc_memalign, uptr align, uptr size) { - void *const res = REAL(__libc_memalign)(align, size); - if (res) - __nsan_set_value_unknown(static_cast(res), size); - return res; + return nsan_memalign(align, size); } #endif diff --git a/compiler-rt/lib/nsan/nsan_new_delete.cpp b/compiler-rt/lib/nsan/nsan_new_delete.cpp new file mode 100644 index 000000000000000..f203a583f2c4480 --- /dev/null +++ b/compiler-rt/lib/nsan/nsan_new_delete.cpp @@ -0,0 +1,126 @@ +//===-- nsan_new_delete.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 is a part of MemorySanitizer. +// +// Interceptors for operators new and delete. +//===----------------------------------------------------------------------===// + +#include "interception/interception.h" +#include "nsan.h" +#include "nsan_allocator.h" +#include "sanitizer_common/sanitizer_allocator.h" +#include "sanitizer_common/sanitizer_allocator_report.h" + +#include + +using namespace __nsan; + +// Fake std::nothrow_t and std::align_val_t to avoid including . +namespace std { +struct nothrow_t {}; +enum class align_val_t : size_t {}; +} // namespace std + +#define OPERATOR_NEW_BODY(nothrow) \ + void *res = nsan_malloc(size); \ + if (!nothrow && UNLIKELY(!res)) { \ + BufferedStackTrace stack; \ + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); \ + ReportOutOfMemory(size, &stack); \ + } \ + return res +#define OPERATOR_NEW_BODY_ALIGN(nothrow) \ + void *res = nsan_memalign((uptr)align, size); \ + if (!nothrow && UNLIKELY(!res)) { \ + BufferedStackTrace stack; \ + GET_FATAL_STACK_TRACE_IF_EMPTY(&stack); \ + ReportOutOfMemory(size, &stack); \ + } \ + return res; + +INTERCEPTOR_ATTRIBUTE +void *operator new(size_t size) { OPERATOR_NEW_BODY(/*nothrow=*/false); } +INTERCEPTOR_ATTRIBUTE +void *operator new[](size_t size) { OPERATOR_NEW_BODY(/*nothrow=*/false); } +INTERCEPTOR_ATTRIBUTE +void *operator new(size_t size, std::nothrow_t const &) { + OPERATOR_NEW_BODY(/*nothrow=*/true); +} +INTERCEPTOR_ATTRIBUTE +void *operator new[](size_t size, std::nothrow_t const &) { + OPERATOR_NEW_BODY(/*nothrow=*/true); +} +INTERCEPTOR_ATTRIBUTE +void *operator new(size_t size, std::align_val_t align) { + OPERATOR_NEW_BODY_ALIGN(/*nothrow=*/false); +} +INTERCEPTOR_ATTRIBUTE +void *operator new[](size_t size, std::align_val_t align) { + OPERATOR_NEW_BODY_ALIGN(/*nothrow=*/false); +} +INTERCEPTOR_ATTRIBUTE +void *operator new(size_t size, std::align_val_t align, + std::nothrow_t const &) { + OPERATOR_NEW_BODY_ALIGN(/*nothrow=*/true); +} +INTERCEPTOR_ATTRIBUTE +void *operator new[](size_t size, std::align_val_t align, + std::nothrow_t const &) { + OPERATOR_NEW_BODY_ALIGN(/*nothrow=*/true); +} + +#define OPERATOR_DELETE_BODY \ + if (ptr) \ + NsanDeallocate(ptr) + +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr) NOEXCEPT { OPERATOR_DELETE_BODY; } +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr) NOEXCEPT { OPERATOR_DELETE_BODY; } +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, std::nothrow_t const &) { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, std::nothrow_t const &) { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, size_t size) NOEXCEPT { OPERATOR_DELETE_BODY; } +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, size_t size) NOEXCEPT { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, std::align_val_t align) NOEXCEPT { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, std::align_val_t align) NOEXCEPT { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, std::align_val_t align, + std::nothrow_t const &) { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, std::align_val_t align, + std::nothrow_t const &) { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete(void *ptr, size_t size, std::align_val_t align) NOEXCEPT { + OPERATOR_DELETE_BODY; +} +INTERCEPTOR_ATTRIBUTE +void operator delete[](void *ptr, size_t size, + std::align_val_t align) NOEXCEPT { + OPERATOR_DELETE_BODY; +} diff --git a/compiler-rt/lib/nsan/nsan_platform.h b/compiler-rt/lib/nsan/nsan_platform.h index c9d4cacd8c8898b..cc9d63ecb4a9baf 100644 --- a/compiler-rt/lib/nsan/nsan_platform.h +++ b/compiler-rt/lib/nsan/nsan_platform.h @@ -40,7 +40,9 @@ namespace __nsan { // | | // | unused | // | | -// +--------------------+ 0x400000000000 (kUnusedAddr) +// +--------------------+ 0x440000008000 +// | allocator | +// +--------------------+ 0x400000000000 (kHeapMemBeg) // | shadow memory | // +--------------------+ 0x200000000000 (kShadowAddr) // | shadow types | @@ -79,7 +81,7 @@ enum { struct Mapping { // FIXME: kAppAddr == 0x700000000000 ? static const uptr kAppAddr = 0x700000008000; - static const uptr kUnusedAddr = 0x400000000000; + static const uptr kHeapMemBeg = 0x400000000000; static const uptr kShadowAddr = 0x200000000000; static const uptr kTypesAddr = 0x100000000000; static const uptr kShadowMask = ~0x700000000000; @@ -90,7 +92,7 @@ struct Mapping { enum MappingType { MAPPING_APP_ADDR, - MAPPING_UNUSED_ADDR, + MAPPING_ALLOCATOR_ADDR, MAPPING_SHADOW_ADDR, MAPPING_TYPES_ADDR, MAPPING_SHADOW_MASK @@ -100,8 +102,8 @@ template uptr MappingImpl() { switch (Type) { case MAPPING_APP_ADDR: return Mapping::kAppAddr; - case MAPPING_UNUSED_ADDR: - return Mapping::kUnusedAddr; + case MAPPING_ALLOCATOR_ADDR: + return Mapping::kHeapMemBeg; case MAPPING_SHADOW_ADDR: return Mapping::kShadowAddr; case MAPPING_TYPES_ADDR: @@ -119,7 +121,7 @@ ALWAYS_INLINE uptr AppAddr() { return MappingArchImpl(); } ALWAYS_INLINE -uptr UnusedAddr() { return MappingArchImpl(); } +uptr AllocatorAddr() { return MappingArchImpl(); } ALWAYS_INLINE uptr ShadowAddr() { return MappingArchImpl(); } diff --git a/compiler-rt/lib/nsan/nsan_thread.cpp b/compiler-rt/lib/nsan/nsan_thread.cpp index 273c46831cf3810..85706aea80ebd1a 100644 --- a/compiler-rt/lib/nsan/nsan_thread.cpp +++ b/compiler-rt/lib/nsan/nsan_thread.cpp @@ -55,6 +55,7 @@ void NsanThread::ClearShadowForThreadStackAndTLS() { void NsanThread::Init() { SetThreadStackAndTls(); ClearShadowForThreadStackAndTLS(); + malloc_storage().Init(); } void NsanThread::TSDDtor(void *tsd) { @@ -63,6 +64,7 @@ void NsanThread::TSDDtor(void *tsd) { } void NsanThread::Destroy() { + malloc_storage().CommitBack(); // We also clear the shadow on thread destruction because // some code may still be executing in later TSD destructors // and we don't want it to have any poisoned stack. diff --git a/compiler-rt/lib/nsan/nsan_thread.h b/compiler-rt/lib/nsan/nsan_thread.h index 18f24fd6f1d78a9..143e61f37db96bb 100644 --- a/compiler-rt/lib/nsan/nsan_thread.h +++ b/compiler-rt/lib/nsan/nsan_thread.h @@ -9,6 +9,7 @@ #ifndef NSAN_THREAD_H #define NSAN_THREAD_H +#include "nsan_allocator.h" #include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_posix.h" @@ -34,6 +35,8 @@ class NsanThread { void StartSwitchFiber(uptr bottom, uptr size); void FinishSwitchFiber(uptr *bottom_old, uptr *size_old); + NsanThreadLocalMallocStorage &malloc_storage() { return malloc_storage_; } + int destructor_iterations_; __sanitizer_sigset_t starting_sigset_; @@ -56,6 +59,8 @@ class NsanThread { uptr tls_begin_; uptr tls_end_; + + NsanThreadLocalMallocStorage malloc_storage_; }; NsanThread *GetCurrentThread(); diff --git a/compiler-rt/lib/scudo/standalone/list.h b/compiler-rt/lib/scudo/standalone/list.h index 0137667d1dcf3ef..6b952a610e30557 100644 --- a/compiler-rt/lib/scudo/standalone/list.h +++ b/compiler-rt/lib/scudo/standalone/list.h @@ -11,17 +11,113 @@ #include "internal_defs.h" +// TODO: Move the helpers to a header. +namespace { +template struct isPointer { + static constexpr bool value = false; +}; + +template struct isPointer { + static constexpr bool value = true; +}; +} // namespace + namespace scudo { // Intrusive POD singly and doubly linked list. // An object with all zero fields should represent a valid empty list. clear() // should be called on all non-zero-initialized objects before using. +// +// The intrusive list requires the member `Next` (and `Prev` if doubly linked +// list)` defined in the node type. The type of `Next`/`Prev` can be a pointer +// or an index to an array. For example, if the storage of the nodes is an +// array, instead of using a pointer type, linking with an index type can save +// some space. +// +// There are two things to be noticed while using an index type, +// 1. Call init() to set up the base address of the array. +// 2. Define `EndOfListVal` as the nil of the list. + +template ::value> +class LinkOp { +public: + LinkOp() = default; + LinkOp(UNUSED T *BaseT, UNUSED uptr BaseSize) {} + void init(UNUSED T *LinkBase, UNUSED uptr Size) {} + T *getBase() const { return nullptr; } + uptr getSize() const { return 0; } + + T *getNext(T *X) const { return X->Next; } + void setNext(T *X, T *Next) const { X->Next = Next; } + + T *getPrev(T *X) const { return X->Prev; } + void setPrev(T *X, T *Prev) const { X->Prev = Prev; } + + T *getEndOfListVal() const { return nullptr; } +}; + +template class LinkOp { +public: + using LinkTy = decltype(T::Next); + + LinkOp() = default; + LinkOp(T *BaseT, uptr BaseSize) : Base(BaseT), Size(BaseSize) {} + void init(T *LinkBase, uptr BaseSize) { + Base = LinkBase; + // TODO: Check if the `BaseSize` can fit in `Size`. + Size = static_cast(BaseSize); + } + T *getBase() const { return Base; } + LinkTy getSize() const { return Size; } + + T *getNext(T *X) const { + DCHECK_NE(getBase(), nullptr); + if (X->Next == getEndOfListVal()) + return nullptr; + DCHECK_LT(X->Next, Size); + return &Base[X->Next]; + } + // Set `X->Next` to `Next`. + void setNext(T *X, T *Next) const { + // TODO: Check if the offset fits in the size of `LinkTy`. + if (Next == nullptr) + X->Next = getEndOfListVal(); + else + X->Next = static_cast(Next - Base); + } -template class IteratorBase { + T *getPrev(T *X) const { + DCHECK_NE(getBase(), nullptr); + if (X->Prev == getEndOfListVal()) + return nullptr; + DCHECK_LT(X->Prev, Size); + return &Base[X->Prev]; + } + // Set `X->Prev` to `Prev`. + void setPrev(T *X, T *Prev) const { + DCHECK_LT(reinterpret_cast(Prev), + reinterpret_cast(Base + Size)); + if (Prev == nullptr) + X->Prev = getEndOfListVal(); + else + X->Prev = static_cast(Prev - Base); + } + + // TODO: `LinkTy` should be the same as decltype(T::EndOfListVal). + LinkTy getEndOfListVal() const { return T::EndOfListVal; } + +protected: + T *Base = nullptr; + LinkTy Size = 0; +}; + +template class IteratorBase : public LinkOp { public: - explicit IteratorBase(T *CurrentT) : Current(CurrentT) {} + IteratorBase(const LinkOp &Link, T *CurrentT) + : LinkOp(Link), Current(CurrentT) {} + IteratorBase &operator++() { - Current = Current->Next; + Current = this->getNext(Current); return *this; } bool operator!=(IteratorBase Other) const { return Current != Other.Current; } @@ -31,7 +127,10 @@ template class IteratorBase { T *Current; }; -template struct IntrusiveList { +template struct IntrusiveList : public LinkOp { + IntrusiveList() = default; + void init(T *Base, uptr BaseSize) { LinkOp::init(Base, BaseSize); } + bool empty() const { return Size == 0; } uptr size() const { return Size; } @@ -48,11 +147,21 @@ template struct IntrusiveList { typedef IteratorBase Iterator; typedef IteratorBase ConstIterator; - Iterator begin() { return Iterator(First); } - Iterator end() { return Iterator(nullptr); } + Iterator begin() { + return Iterator(LinkOp(this->getBase(), this->getSize()), First); + } + Iterator end() { + return Iterator(LinkOp(this->getBase(), this->getSize()), nullptr); + } - ConstIterator begin() const { return ConstIterator(First); } - ConstIterator end() const { return ConstIterator(nullptr); } + ConstIterator begin() const { + return ConstIterator(LinkOp(this->getBase(), this->getSize()), + First); + } + ConstIterator end() const { + return ConstIterator(LinkOp(this->getBase(), this->getSize()), + nullptr); + } void checkConsistency() const; @@ -68,13 +177,13 @@ template void IntrusiveList::checkConsistency() const { CHECK_EQ(Last, nullptr); } else { uptr Count = 0; - for (T *I = First;; I = I->Next) { + for (T *I = First;; I = this->getNext(I)) { Count++; if (I == Last) break; } CHECK_EQ(this->size(), Count); - CHECK_EQ(Last->Next, nullptr); + CHECK_EQ(this->getNext(Last), nullptr); } } @@ -83,13 +192,16 @@ template struct SinglyLinkedList : public IntrusiveList { using IntrusiveList::Last; using IntrusiveList::Size; using IntrusiveList::empty; + using IntrusiveList::setNext; + using IntrusiveList::getNext; + using IntrusiveList::getEndOfListVal; void push_back(T *X) { - X->Next = nullptr; + setNext(X, nullptr); if (empty()) First = X; else - Last->Next = X; + setNext(Last, X); Last = X; Size++; } @@ -97,14 +209,14 @@ template struct SinglyLinkedList : public IntrusiveList { void push_front(T *X) { if (empty()) Last = X; - X->Next = First; + setNext(X, First); First = X; Size++; } void pop_front() { DCHECK(!empty()); - First = First->Next; + First = getNext(First); if (!First) Last = nullptr; Size--; @@ -115,8 +227,8 @@ template struct SinglyLinkedList : public IntrusiveList { DCHECK(!empty()); DCHECK_NE(Prev, nullptr); DCHECK_NE(X, nullptr); - X->Next = Prev->Next; - Prev->Next = X; + setNext(X, getNext(Prev)); + setNext(Prev, X); if (Last == Prev) Last = X; ++Size; @@ -126,8 +238,8 @@ template struct SinglyLinkedList : public IntrusiveList { DCHECK(!empty()); DCHECK_NE(Prev, nullptr); DCHECK_NE(X, nullptr); - DCHECK_EQ(Prev->Next, X); - Prev->Next = X->Next; + DCHECK_EQ(getNext(Prev), X); + setNext(Prev, getNext(X)); if (Last == X) Last = Prev; Size--; @@ -140,7 +252,7 @@ template struct SinglyLinkedList : public IntrusiveList { if (empty()) { *this = *L; } else { - Last->Next = L->First; + setNext(Last, L->First); Last = L->Last; Size += L->size(); } @@ -153,16 +265,21 @@ template struct DoublyLinkedList : IntrusiveList { using IntrusiveList::Last; using IntrusiveList::Size; using IntrusiveList::empty; + using IntrusiveList::setNext; + using IntrusiveList::getNext; + using IntrusiveList::setPrev; + using IntrusiveList::getPrev; + using IntrusiveList::getEndOfListVal; void push_front(T *X) { - X->Prev = nullptr; + setPrev(X, nullptr); if (empty()) { Last = X; } else { - DCHECK_EQ(First->Prev, nullptr); - First->Prev = X; + DCHECK_EQ(getPrev(First), nullptr); + setPrev(First, X); } - X->Next = First; + setNext(X, First); First = X; Size++; } @@ -171,37 +288,37 @@ template struct DoublyLinkedList : IntrusiveList { void insert(T *X, T *Y) { if (Y == First) return push_front(X); - T *Prev = Y->Prev; + T *Prev = getPrev(Y); // This is a hard CHECK to ensure consistency in the event of an intentional // corruption of Y->Prev, to prevent a potential write-{4,8}. - CHECK_EQ(Prev->Next, Y); - Prev->Next = X; - X->Prev = Prev; - X->Next = Y; - Y->Prev = X; + CHECK_EQ(getNext(Prev), Y); + setNext(Prev, X); + setPrev(X, Prev); + setNext(X, Y); + setPrev(Y, X); Size++; } void push_back(T *X) { - X->Next = nullptr; + setNext(X, nullptr); if (empty()) { First = X; } else { - DCHECK_EQ(Last->Next, nullptr); - Last->Next = X; + DCHECK_EQ(getNext(Last), nullptr); + setNext(Last, X); } - X->Prev = Last; + setPrev(X, Last); Last = X; Size++; } void pop_front() { DCHECK(!empty()); - First = First->Next; + First = getNext(First); if (!First) Last = nullptr; else - First->Prev = nullptr; + setPrev(First, nullptr); Size--; } @@ -209,15 +326,15 @@ template struct DoublyLinkedList : IntrusiveList { // catch potential corruption attempts, that could yield a mirrored // write-{4,8} primitive. nullptr checks are deemed less vital. void remove(T *X) { - T *Prev = X->Prev; - T *Next = X->Next; + T *Prev = getPrev(X); + T *Next = getNext(X); if (Prev) { - CHECK_EQ(Prev->Next, X); - Prev->Next = Next; + CHECK_EQ(getNext(Prev), X); + setNext(Prev, Next); } if (Next) { - CHECK_EQ(Next->Prev, X); - Next->Prev = Prev; + CHECK_EQ(getPrev(Next), X); + setPrev(Next, Prev); } if (First == X) { DCHECK_EQ(Prev, nullptr); diff --git a/compiler-rt/lib/scudo/standalone/tests/list_test.cpp b/compiler-rt/lib/scudo/standalone/tests/list_test.cpp index 140ca027ae92836..688cbbef6a032f2 100644 --- a/compiler-rt/lib/scudo/standalone/tests/list_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/list_test.cpp @@ -10,25 +10,22 @@ #include "list.h" -struct ListItem { - ListItem *Next; - ListItem *Prev; -}; +#include -static ListItem Items[6]; -static ListItem *X = &Items[0]; -static ListItem *Y = &Items[1]; -static ListItem *Z = &Items[2]; -static ListItem *A = &Items[3]; -static ListItem *B = &Items[4]; -static ListItem *C = &Items[5]; +struct ListItemLinkedWithPtr { + ListItemLinkedWithPtr *Next; + ListItemLinkedWithPtr *Prev; +}; -typedef scudo::SinglyLinkedList SLList; -typedef scudo::DoublyLinkedList DLList; +struct ListItemLinkedWithIndex { + scudo::uptr Next; + scudo::uptr Prev; + static constexpr scudo::uptr EndOfListVal = 1ULL << 30; +}; -template -static void setList(ListT *L, ListItem *I1 = nullptr, ListItem *I2 = nullptr, - ListItem *I3 = nullptr) { +template +static void setList(ListT *L, ListItemTy *I1 = nullptr, + ListItemTy *I2 = nullptr, ListItemTy *I3 = nullptr) { L->clear(); if (I1) L->push_back(I1); @@ -38,10 +35,10 @@ static void setList(ListT *L, ListItem *I1 = nullptr, ListItem *I2 = nullptr, L->push_back(I3); } -template -static void checkList(ListT *L, ListItem *I1, ListItem *I2 = nullptr, - ListItem *I3 = nullptr, ListItem *I4 = nullptr, - ListItem *I5 = nullptr, ListItem *I6 = nullptr) { +template +static void checkList(ListT *L, ListItemTy *I1, ListItemTy *I2 = nullptr, + ListItemTy *I3 = nullptr, ListItemTy *I4 = nullptr, + ListItemTy *I5 = nullptr, ListItemTy *I6 = nullptr) { if (I1) { EXPECT_EQ(L->front(), I1); L->pop_front(); @@ -69,9 +66,16 @@ static void checkList(ListT *L, ListItem *I1, ListItem *I2 = nullptr, EXPECT_TRUE(L->empty()); } -template static void testListCommon(void) { - ListT L; +template