Browse Source

ClangPlugins: Add LLVM lit test suite

Matthew Olsson 1 year ago
parent
commit
46ee2b5f06

+ 38 - 0
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 .
+)

+ 23 - 0
Tests/ClangPlugins/LambdaTests/lambda_capture_local_by_ref.cpp

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <AK/Function.h>
+
+// expected-note@+1 {{Annotate the parameter with NOESCAPE if the lambda will not outlive the function call}}
+void take_fn(Function<void()>) { }
+
+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;
+    });
+}

+ 20 - 0
Tests/ClangPlugins/LambdaTests/lambda_capture_local_marked_ignore_by_ref.cpp

@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+// expected-no-diagnostics
+
+#include <AK/Function.h>
+
+void take_fn(Function<void()>) { }
+
+void test()
+{
+    IGNORE_USE_IN_ESCAPING_LAMBDA int a = 0;
+    take_fn([&a] {
+        (void)a;
+    });
+}

+ 20 - 0
Tests/ClangPlugins/LambdaTests/lambda_marked_noescape_capture_local_by_ref.cpp

@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+// expected-no-diagnostics
+
+#include <AK/Function.h>
+
+void take_fn(NOESCAPE Function<void()>) { }
+
+void test()
+{
+    int a = 0;
+    take_fn([&a] {
+        (void)a;
+    });
+}

+ 19 - 0
Tests/ClangPlugins/LibJSGCTests/calling_base_visit_edges_not_through_using_decl.cpp

@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <LibJS/Runtime/Object.h>
+
+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);
+    }
+};

+ 63 - 0
Tests/ClangPlugins/LibJSGCTests/cell_member_not_wrapped.cpp

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <LibJS/Runtime/Object.h>
+
+// 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<JS::Object*> 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<JS::Object*> 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;
+};

+ 24 - 0
Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_accessed.cpp

@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+// expected-no-diagnostics
+
+#include <LibJS/Runtime/Object.h>
+
+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<JS::Object> m_object;
+};

+ 22 - 0
Tests/ClangPlugins/LibJSGCTests/gc_allocated_member_is_visited.cpp

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+// expected-no-diagnostics
+
+#include <LibJS/Runtime/Object.h>
+
+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<JS::Object> m_object;
+};

+ 18 - 0
Tests/ClangPlugins/LibJSGCTests/missing_call_to_base_visit_edges.cpp

@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <LibJS/Runtime/Object.h>
+
+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
+    {
+    }
+};

+ 21 - 0
Tests/ClangPlugins/LibJSGCTests/missing_member_in_visit_edges.cpp

@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <LibJS/Runtime/Object.h>
+
+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<JS::Object> m_object;
+};

+ 16 - 0
Tests/ClangPlugins/LibJSGCTests/missing_visit_edges_method.cpp

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <LibJS/Runtime/Object.h>
+
+// 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<JS::Object> m_cell;
+};

+ 14 - 0
Tests/ClangPlugins/LibJSGCTests/non_cell_class.cpp

@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+// expected-no-diagnostics
+
+#include <LibJS/Runtime/Object.h>
+
+class NonCell {
+    JS::GCPtr<JS::Object> m_object;
+};

+ 16 - 0
Tests/ClangPlugins/LibJSGCTests/not_visiting_rawgcptr.cpp

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+// expected-no-diagnostics
+
+#include <LibJS/Runtime/Object.h>
+
+class TestClass : public JS::Object {
+    JS_OBJECT(TestClass, JS::Object);
+
+    JS::RawGCPtr<JS::Object> m_object;
+};

+ 21 - 0
Tests/ClangPlugins/LibJSGCTests/wrapping_non_cell_member.cpp

@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2024, Matthew Olsson <mattco@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// RUN: %clang++ -cc1 -verify %plugin_opts% %s 2>&1
+
+#include <LibJS/Heap/GCPtr.h>
+#include <LibJS/Heap/MarkedVector.h>
+
+struct NotACell { };
+
+class TestClass {
+    // expected-warning@+1 {{Specialization type must inherit from JS::Cell}}
+    JS::GCPtr<NotACell> m_member_1;
+    // expected-warning@+1 {{Specialization type must inherit from JS::Cell}}
+    JS::NonnullGCPtr<NotACell> m_member_2;
+    // expected-warning@+1 {{Specialization type must inherit from JS::Cell}}
+    JS::RawGCPtr<NotACell> m_member_3;
+};

+ 25 - 0
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)

+ 29 - 0
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"))

+ 1 - 0
Tests/ClangPlugins/requirements.txt

@@ -0,0 +1 @@
+lit==18.1.3