From 46ee2b5f06db423129acd62db09dfb176e7db684 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Mon, 15 Apr 2024 20:07:30 -0700 Subject: [PATCH] ClangPlugins: Add LLVM lit test suite --- Tests/ClangPlugins/CMakeLists.txt | 38 +++++++++++ .../lambda_capture_local_by_ref.cpp | 23 +++++++ ...bda_capture_local_marked_ignore_by_ref.cpp | 20 ++++++ ...a_marked_noescape_capture_local_by_ref.cpp | 20 ++++++ ...ase_visit_edges_not_through_using_decl.cpp | 19 ++++++ .../LibJSGCTests/cell_member_not_wrapped.cpp | 63 +++++++++++++++++++ .../gc_allocated_member_is_accessed.cpp | 24 +++++++ .../gc_allocated_member_is_visited.cpp | 22 +++++++ .../missing_call_to_base_visit_edges.cpp | 18 ++++++ .../missing_member_in_visit_edges.cpp | 21 +++++++ .../missing_visit_edges_method.cpp | 16 +++++ .../LibJSGCTests/non_cell_class.cpp | 14 +++++ .../LibJSGCTests/not_visiting_rawgcptr.cpp | 16 +++++ .../LibJSGCTests/wrapping_non_cell_member.cpp | 21 +++++++ Tests/ClangPlugins/lit.cfg.py | 25 ++++++++ Tests/ClangPlugins/lit.site.cfg.py.in | 29 +++++++++ Tests/ClangPlugins/requirements.txt | 1 + 17 files changed, 390 insertions(+) create mode 100644 Tests/ClangPlugins/CMakeLists.txt create mode 100644 Tests/ClangPlugins/LambdaTests/lambda_capture_local_by_ref.cpp create mode 100644 Tests/ClangPlugins/LambdaTests/lambda_capture_local_marked_ignore_by_ref.cpp create mode 100644 Tests/ClangPlugins/LambdaTests/lambda_marked_noescape_capture_local_by_ref.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/calling_base_visit_edges_not_through_using_decl.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/cell_member_not_wrapped.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_accessed.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_visited.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/missing_call_to_base_visit_edges.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/missing_member_in_visit_edges.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/missing_visit_edges_method.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/non_cell_class.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/not_visiting_rawgcptr.cpp create mode 100644 Tests/ClangPlugins/LibJSGCTests/wrapping_non_cell_member.cpp create mode 100644 Tests/ClangPlugins/lit.cfg.py create mode 100644 Tests/ClangPlugins/lit.site.cfg.py.in create mode 100644 Tests/ClangPlugins/requirements.txt diff --git a/Tests/ClangPlugins/CMakeLists.txt b/Tests/ClangPlugins/CMakeLists.txt new file mode 100644 index 00000000000..12d1457694f --- /dev/null +++ b/Tests/ClangPlugins/CMakeLists.txt @@ -0,0 +1,38 @@ +find_package(Clang 17 CONFIG REQUIRED) +find_package(LLVM 17 CONFIG REQUIRED) +include(AddLLVM) + +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +get_property(CLANG_PLUGINS_ALL_COMPILE_OPTIONS GLOBAL PROPERTY CLANG_PLUGINS_ALL_COMPILE_OPTIONS) +list(APPEND CLANG_PLUGINS_ALL_COMPILE_OPTIONS -std=c++20 -Wno-user-defined-literals -Wno-literal-range) + +get_property(CLANG_PLUGINS_INCLUDE_DIRECTORIES TARGET AK PROPERTY INCLUDE_DIRECTORIES) +list(APPEND CLANG_PLUGINS_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py + MAIN_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py + PATHS + LLVM_BINARY_DIR + LLVM_TOOLS_DIR + LLVM_LIBS_DIR + CMAKE_LIBRARY_OUTPUT_DIRECTORY + CMAKE_CURRENT_SOURCE_DIR +) + +add_custom_command( + OUTPUT venv + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt requirements.txt + COMMAND ${Python3_EXECUTABLE} -m venv venv + COMMAND ./venv/bin/pip install -r requirements.txt --upgrade +) +add_custom_target(TestClangPluginsDependencies ALL + DEPENDS venv JSClangPlugin GenericClangPlugin + SOURCES requirements.txt +) +add_test( + NAME TestClangPlugins + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/venv/bin/lit -v . +) diff --git a/Tests/ClangPlugins/LambdaTests/lambda_capture_local_by_ref.cpp b/Tests/ClangPlugins/LambdaTests/lambda_capture_local_by_ref.cpp new file mode 100644 index 00000000000..2fbcb7a9cfc --- /dev/null +++ b/Tests/ClangPlugins/LambdaTests/lambda_capture_local_by_ref.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include + +// expected-note@+1 {{Annotate the parameter with NOESCAPE if the lambda will not outlive the function call}} +void take_fn(Function) { } + +void test() +{ + // expected-note@+1 {{Annotate the variable declaration with IGNORE_USE_IN_ESCAPING_LAMBDA if it outlives the lambda}} + int a = 0; + + // expected-warning@+1 {{Variable with local storage is captured by reference in a lambda that may be asynchronously executed}} + take_fn([&a] { + (void)a; + }); +} diff --git a/Tests/ClangPlugins/LambdaTests/lambda_capture_local_marked_ignore_by_ref.cpp b/Tests/ClangPlugins/LambdaTests/lambda_capture_local_marked_ignore_by_ref.cpp new file mode 100644 index 00000000000..496ccdc6190 --- /dev/null +++ b/Tests/ClangPlugins/LambdaTests/lambda_capture_local_marked_ignore_by_ref.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 +// expected-no-diagnostics + +#include + +void take_fn(Function) { } + +void test() +{ + IGNORE_USE_IN_ESCAPING_LAMBDA int a = 0; + take_fn([&a] { + (void)a; + }); +} diff --git a/Tests/ClangPlugins/LambdaTests/lambda_marked_noescape_capture_local_by_ref.cpp b/Tests/ClangPlugins/LambdaTests/lambda_marked_noescape_capture_local_by_ref.cpp new file mode 100644 index 00000000000..5d52e310b62 --- /dev/null +++ b/Tests/ClangPlugins/LambdaTests/lambda_marked_noescape_capture_local_by_ref.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 +// expected-no-diagnostics + +#include + +void take_fn(NOESCAPE Function) { } + +void test() +{ + int a = 0; + take_fn([&a] { + (void)a; + }); +} diff --git a/Tests/ClangPlugins/LibJSGCTests/calling_base_visit_edges_not_through_using_decl.cpp b/Tests/ClangPlugins/LibJSGCTests/calling_base_visit_edges_not_through_using_decl.cpp new file mode 100644 index 00000000000..9356b8e12f3 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/calling_base_visit_edges_not_through_using_decl.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + // expected-warning@+1 {{Missing call to Base::visit_edges}} + virtual void visit_edges(Visitor& visitor) override + { + JS::Object::visit_edges(visitor); + } +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/cell_member_not_wrapped.cpp b/Tests/ClangPlugins/LibJSGCTests/cell_member_not_wrapped.cpp new file mode 100644 index 00000000000..0a282764555 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/cell_member_not_wrapped.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include + +// Ensure it can see through typedefs +typedef JS::Object NewType1; +using NewType2 = JS::Object; + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + +public: + explicit TestClass(JS::Realm& realm, JS::Object& obj) + : JS::Object(realm, nullptr) + , m_object_ref(obj) + { + } + +private: + virtual void visit_edges(Visitor& visitor) override + { + Base::visit_edges(visitor); + visitor.visit(m_object_ref); + visitor.visit(m_object_ptr); + } + + // expected-warning@+1 {{reference to JS::Cell type should be wrapped in JS::NonnullGCPtr}} + JS::Object& m_object_ref; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + JS::Object* m_object_ptr; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + Vector m_objects; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + NewType1* m_newtype_1; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + NewType2* m_newtype_2; +}; + +class TestClassNonCell { +public: + explicit TestClassNonCell(JS::Object& obj) + : m_object_ref(obj) + { + } + +private: + // expected-warning@+1 {{reference to JS::Cell type should be wrapped in JS::NonnullGCPtr}} + JS::Object& m_object_ref; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + JS::Object* m_object_ptr; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + Vector m_objects; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + NewType1* m_newtype_1; + // expected-warning@+1 {{pointer to JS::Cell type should be wrapped in JS::GCPtr}} + NewType2* m_newtype_2; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_accessed.cpp b/Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_accessed.cpp new file mode 100644 index 00000000000..ee534facaf8 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_accessed.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 +// expected-no-diagnostics + +#include + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + virtual void visit_edges(Visitor& visitor) override + { + Base::visit_edges(visitor); + + // FIXME: It might be nice to check that the object is specifically passed to .visit() or .ignore() + (void)m_object; + } + + JS::GCPtr m_object; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_visited.cpp b/Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_visited.cpp new file mode 100644 index 00000000000..15e3a8e24b7 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_visited.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 +// expected-no-diagnostics + +#include + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + virtual void visit_edges(Visitor& visitor) override + { + Base::visit_edges(visitor); + visitor.visit(m_object); + } + + JS::GCPtr m_object; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/missing_call_to_base_visit_edges.cpp b/Tests/ClangPlugins/LibJSGCTests/missing_call_to_base_visit_edges.cpp new file mode 100644 index 00000000000..ce45cc2cd80 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/missing_call_to_base_visit_edges.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + // expected-warning@+1 {{Missing call to Base::visit_edges}} + virtual void visit_edges(Visitor&) override + { + } +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/missing_member_in_visit_edges.cpp b/Tests/ClangPlugins/LibJSGCTests/missing_member_in_visit_edges.cpp new file mode 100644 index 00000000000..e6ea571ad1f --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/missing_member_in_visit_edges.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + virtual void visit_edges(Visitor& visitor) override + { + Base::visit_edges(visitor); + } + + // expected-warning@+1 {{GC-allocated member is not visited in TestClass::visit_edges}} + JS::GCPtr m_object; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/missing_visit_edges_method.cpp b/Tests/ClangPlugins/LibJSGCTests/missing_visit_edges_method.cpp new file mode 100644 index 00000000000..fb810bbb033 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/missing_visit_edges_method.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include + +// expected-warning@+1 {{JS::Cell-inheriting class TestClass contains a GC-allocated member 'm_cell' but has no visit_edges method}} +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + JS::GCPtr m_cell; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/non_cell_class.cpp b/Tests/ClangPlugins/LibJSGCTests/non_cell_class.cpp new file mode 100644 index 00000000000..c77abf0a081 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/non_cell_class.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 +// expected-no-diagnostics + +#include + +class NonCell { + JS::GCPtr m_object; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/not_visiting_rawgcptr.cpp b/Tests/ClangPlugins/LibJSGCTests/not_visiting_rawgcptr.cpp new file mode 100644 index 00000000000..de541818d73 --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/not_visiting_rawgcptr.cpp @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 +// expected-no-diagnostics + +#include + +class TestClass : public JS::Object { + JS_OBJECT(TestClass, JS::Object); + + JS::RawGCPtr m_object; +}; diff --git a/Tests/ClangPlugins/LibJSGCTests/wrapping_non_cell_member.cpp b/Tests/ClangPlugins/LibJSGCTests/wrapping_non_cell_member.cpp new file mode 100644 index 00000000000..1eebe4cdc4a --- /dev/null +++ b/Tests/ClangPlugins/LibJSGCTests/wrapping_non_cell_member.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1 + +#include +#include + +struct NotACell { }; + +class TestClass { + // expected-warning@+1 {{Specialization type must inherit from JS::Cell}} + JS::GCPtr m_member_1; + // expected-warning@+1 {{Specialization type must inherit from JS::Cell}} + JS::NonnullGCPtr m_member_2; + // expected-warning@+1 {{Specialization type must inherit from JS::Cell}} + JS::RawGCPtr m_member_3; +}; diff --git a/Tests/ClangPlugins/lit.cfg.py b/Tests/ClangPlugins/lit.cfg.py new file mode 100644 index 00000000000..ecc568457a0 --- /dev/null +++ b/Tests/ClangPlugins/lit.cfg.py @@ -0,0 +1,25 @@ +# Disable flake linting for this file since it flags "config" as a non-existent variable +# flake8: noqa + +import os +import lit.formats +import lit.util +from lit.llvm import llvm_config +from lit.llvm.subst import ToolSubst +from lit.llvm.subst import FindTool + +config.name = "ClangPlugins" +config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell) +config.suffixes = [".cpp"] +config.test_source_root = os.path.dirname(__file__) +llvm_config.use_default_substitutions() +llvm_config.use_clang() +config.substitutions.append(("%target_triple", config.target_triple)) +config.substitutions.append(("%PATH%", config.environment["PATH"])) + +plugin_includes = " ".join(f"-I{s}" for s in config.plugin_includes.split(";")) +plugin_opts = " ".join(s.replace("-fplugin=", "-load ") for s in config.plugin_opts.split(";")) +config.substitutions.append(("%plugin_opts%", f"{plugin_opts} {plugin_includes}")) + +tools = ["clang", "clang++"] +llvm_config.add_tool_substitutions(tools, config.llvm_tools_dir) diff --git a/Tests/ClangPlugins/lit.site.cfg.py.in b/Tests/ClangPlugins/lit.site.cfg.py.in new file mode 100644 index 00000000000..f2b4ea638d7 --- /dev/null +++ b/Tests/ClangPlugins/lit.site.cfg.py.in @@ -0,0 +1,29 @@ +@LIT_SITE_CFG_IN_HEADER@ + +import sys + +config.llvm_obj_root = path(r"@LLVM_BINARY_DIR@") +config.llvm_tools_dir = lit_config.substitute(path(r"@LLVM_TOOLS_DIR@")) +config.llvm_libs_dir = lit_config.substitute(path(r"@LLVM_LIBS_DIR@")) +config.llvm_shlib_dir = lit_config.substitute(path(r"@CMAKE_LIBRARY_OUTPUT_DIRECTORY@")) +config.llvm_plugin_ext = "@LLVM_PLUGIN_EXT@" +config.clang_lit_site_cfg = __file__ +config.clang_lib_dir = path(r"@CMAKE_LIBRARY_OUTPUT_DIRECTORY@") +config.host_triple = "@LLVM_HOST_TRIPLE@" +config.target_triple = "@LLVM_TARGET_TRIPLE@" +config.host_cc = "@CMAKE_C_COMPILER@" +config.host_cxx = "@CMAKE_CXX_COMPILER@" +config.have_zlib = @LLVM_ENABLE_ZLIB@ +config.enable_shared = @ENABLE_SHARED@ +config.host_arch = "@HOST_ARCH@" +config.python_executable = "@Python3_EXECUTABLE@" +# config.has_plugins = @CLANG_PLUGIN_SUPPORT@ +config.plugin_opts = "@CLANG_PLUGINS_ALL_COMPILE_OPTIONS@" +config.plugin_includes = "@CLANG_PLUGINS_INCLUDE_DIRECTORIES@" + +import lit.llvm +lit.llvm.initialize(lit_config, config) + +# Let the main config do the real work. +lit_config.load_config( + config, os.path.join(path("@CMAKE_CURRENT_SOURCE_DIR@"), "lit.cfg.py")) diff --git a/Tests/ClangPlugins/requirements.txt b/Tests/ClangPlugins/requirements.txt new file mode 100644 index 00000000000..33f14f41c5a --- /dev/null +++ b/Tests/ClangPlugins/requirements.txt @@ -0,0 +1 @@ +lit==18.1.3