ClangPlugins: Add LLVM lit test suite

This commit is contained in:
Matthew Olsson 2024-04-15 20:07:30 -07:00 committed by Andrew Kaster
parent 6587c1f350
commit 46ee2b5f06
Notes: sideshowbarker 2024-07-16 22:14:49 +09:00
17 changed files with 390 additions and 0 deletions

View File

@ -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 .
)

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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;
});
}

View File

@ -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);
}
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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
{
}
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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)

View File

@ -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"))

View File

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