mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-10-04 00:59:34 +03:00
ClangPlugins: Check for strong root fields in GC allocated objects
GC-allocated objects should never have JS::SafeFunction/JS::Handle fields. For now the plugin only emits warnings here, as there are many cases of this occurring in the codebase that aren't trivial to fix. It is also behind a CMake flag since it is a _very_ loud warning.
This commit is contained in:
parent
9ea6ab0ad4
commit
5740f93ef4
Notes:
sideshowbarker
2024-07-17 01:53:23 +09:00
Author: https://github.com/mattco98 Commit: https://github.com/SerenityOS/serenity/commit/5740f93ef4 Pull-request: https://github.com/SerenityOS/serenity/pull/24422 Issue: https://github.com/SerenityOS/serenity/issues/23880 Issue: https://github.com/SerenityOS/serenity/issues/23881 Issue: https://github.com/SerenityOS/serenity/issues/23900 Reviewed-by: https://github.com/ADKaster ✅
@ -33,3 +33,4 @@ serenity_option(SERENITY_CACHE_DIR "${PROJECT_BINARY_DIR}/../caches" CACHE PATH
|
|||||||
serenity_option(ENABLE_NETWORK_DOWNLOADS ON CACHE BOOL "Allow downloads of required files. If OFF, required files must already be present in SERENITY_CACHE_DIR")
|
serenity_option(ENABLE_NETWORK_DOWNLOADS ON CACHE BOOL "Allow downloads of required files. If OFF, required files must already be present in SERENITY_CACHE_DIR")
|
||||||
|
|
||||||
serenity_option(ENABLE_CLANG_PLUGINS OFF CACHE BOOL "Enable building with the Clang plugins")
|
serenity_option(ENABLE_CLANG_PLUGINS OFF CACHE BOOL "Enable building with the Clang plugins")
|
||||||
|
serenity_option(ENABLE_CLANG_PLUGINS_INVALID_FUNCTION_MEMBERS OFF CACHE BOOL "Enable detecting invalid function types as members of GC-allocated objects")
|
||||||
|
@ -19,6 +19,9 @@ function(depend_on_clang_plugin target_name plugin_name)
|
|||||||
add_dependencies(${target_name} ${plugin_name}Target)
|
add_dependencies(${target_name} ${plugin_name}Target)
|
||||||
endif()
|
endif()
|
||||||
target_compile_options(${target_name} INTERFACE -fplugin=$<TARGET_FILE:Lagom::${plugin_name}>)
|
target_compile_options(${target_name} INTERFACE -fplugin=$<TARGET_FILE:Lagom::${plugin_name}>)
|
||||||
|
if (${ENABLE_CLANG_PLUGINS_INVALID_FUNCTION_MEMBERS})
|
||||||
|
target_compile_options(${target_name} INTERFACE -fplugin-arg-libjs_gc_scanner-detect-invalid-function-members)
|
||||||
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
clang_plugin(LambdaCaptureClangPlugin SOURCES LambdaCapturePluginAction.cpp)
|
clang_plugin(LambdaCaptureClangPlugin SOURCES LambdaCapturePluginAction.cpp)
|
||||||
|
@ -57,7 +57,16 @@ std::vector<clang::QualType> get_all_qualified_types(clang::QualType const& type
|
|||||||
if (auto const* template_specialization = type->getAs<clang::TemplateSpecializationType>()) {
|
if (auto const* template_specialization = type->getAs<clang::TemplateSpecializationType>()) {
|
||||||
auto specialization_name = template_specialization->getTemplateName().getAsTemplateDecl()->getQualifiedNameAsString();
|
auto specialization_name = template_specialization->getTemplateName().getAsTemplateDecl()->getQualifiedNameAsString();
|
||||||
// Do not unwrap GCPtr/NonnullGCPtr/MarkedVector
|
// Do not unwrap GCPtr/NonnullGCPtr/MarkedVector
|
||||||
if (specialization_name == "JS::GCPtr" || specialization_name == "JS::NonnullGCPtr" || specialization_name == "JS::RawGCPtr" || specialization_name == "JS::MarkedVector") {
|
static std::unordered_set<std::string> gc_relevant_type_names {
|
||||||
|
"JS::GCPtr",
|
||||||
|
"JS::NonnullGCPtr",
|
||||||
|
"JS::RawGCPtr",
|
||||||
|
"JS::MarkedVector",
|
||||||
|
"JS::Handle",
|
||||||
|
"JS::SafeFunction",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (gc_relevant_type_names.contains(specialization_name)) {
|
||||||
qualified_types.push_back(type);
|
qualified_types.push_back(type);
|
||||||
} else {
|
} else {
|
||||||
auto const template_arguments = template_specialization->template_arguments();
|
auto const template_arguments = template_specialization->template_arguments();
|
||||||
@ -75,65 +84,75 @@ std::vector<clang::QualType> get_all_qualified_types(clang::QualType const& type
|
|||||||
|
|
||||||
return qualified_types;
|
return qualified_types;
|
||||||
}
|
}
|
||||||
|
enum class OuterType {
|
||||||
struct FieldValidationResult {
|
GCPtr,
|
||||||
bool is_valid { false };
|
RawGCPtr,
|
||||||
bool is_wrapped_in_gcptr { false };
|
Handle,
|
||||||
bool needs_visiting { false };
|
SafeFunction,
|
||||||
|
Ptr,
|
||||||
|
Ref,
|
||||||
};
|
};
|
||||||
|
|
||||||
FieldValidationResult validate_field(clang::FieldDecl const* field_decl)
|
struct QualTypeGCInfo {
|
||||||
|
std::optional<OuterType> outer_type { {} };
|
||||||
|
bool base_type_inherits_from_cell { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<QualTypeGCInfo> validate_qualified_type(clang::QualType const& type)
|
||||||
|
{
|
||||||
|
if (auto const* pointer_decl = type->getAs<clang::PointerType>()) {
|
||||||
|
if (auto const* pointee = pointer_decl->getPointeeCXXRecordDecl())
|
||||||
|
return QualTypeGCInfo { OuterType::Ptr, record_inherits_from_cell(*pointee) };
|
||||||
|
} else if (auto const* reference_decl = type->getAs<clang::ReferenceType>()) {
|
||||||
|
if (auto const* pointee = reference_decl->getPointeeCXXRecordDecl())
|
||||||
|
return QualTypeGCInfo { OuterType::Ref, record_inherits_from_cell(*pointee) };
|
||||||
|
} else if (auto const* specialization = type->getAs<clang::TemplateSpecializationType>()) {
|
||||||
|
auto template_type_name = specialization->getTemplateName().getAsTemplateDecl()->getQualifiedNameAsString();
|
||||||
|
|
||||||
|
OuterType outer_type;
|
||||||
|
if (template_type_name == "JS::GCPtr" || template_type_name == "JS::NonnullGCPtr") {
|
||||||
|
outer_type = OuterType::GCPtr;
|
||||||
|
} else if (template_type_name == "JS::RawGCPtr") {
|
||||||
|
outer_type = OuterType::RawGCPtr;
|
||||||
|
} else if (template_type_name == "JS::Handle") {
|
||||||
|
outer_type = OuterType::Handle;
|
||||||
|
} else if (template_type_name == "JS::SafeFunction") {
|
||||||
|
return QualTypeGCInfo { OuterType::SafeFunction, false };
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto template_args = specialization->template_arguments();
|
||||||
|
if (template_args.size() != 1)
|
||||||
|
return {}; // Not really valid, but will produce a compilation error anyway
|
||||||
|
|
||||||
|
auto const& type_arg = template_args[0];
|
||||||
|
auto const* record_type = type_arg.getAsType()->getAs<clang::RecordType>();
|
||||||
|
if (!record_type)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto const* record_decl = record_type->getAsCXXRecordDecl();
|
||||||
|
if (!record_decl->hasDefinition())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return QualTypeGCInfo { outer_type, record_inherits_from_cell(*record_decl) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QualTypeGCInfo> validate_field_qualified_type(clang::FieldDecl const* field_decl)
|
||||||
{
|
{
|
||||||
auto type = field_decl->getType();
|
auto type = field_decl->getType();
|
||||||
if (auto const* elaborated_type = llvm::dyn_cast<clang::ElaboratedType>(type.getTypePtr()))
|
if (auto const* elaborated_type = llvm::dyn_cast<clang::ElaboratedType>(type.getTypePtr()))
|
||||||
type = elaborated_type->desugar();
|
type = elaborated_type->desugar();
|
||||||
|
|
||||||
FieldValidationResult result { .is_valid = true };
|
|
||||||
|
|
||||||
for (auto const& qualified_type : get_all_qualified_types(type)) {
|
for (auto const& qualified_type : get_all_qualified_types(type)) {
|
||||||
if (auto const* pointer_decl = qualified_type->getAs<clang::PointerType>()) {
|
if (auto error = validate_qualified_type(qualified_type))
|
||||||
if (auto const* pointee = pointer_decl->getPointeeCXXRecordDecl()) {
|
return error;
|
||||||
if (record_inherits_from_cell(*pointee)) {
|
|
||||||
result.is_valid = false;
|
|
||||||
result.is_wrapped_in_gcptr = false;
|
|
||||||
result.needs_visiting = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (auto const* reference_decl = qualified_type->getAs<clang::ReferenceType>()) {
|
|
||||||
if (auto const* pointee = reference_decl->getPointeeCXXRecordDecl()) {
|
|
||||||
if (record_inherits_from_cell(*pointee)) {
|
|
||||||
result.is_valid = false;
|
|
||||||
result.is_wrapped_in_gcptr = false;
|
|
||||||
result.needs_visiting = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (auto const* specialization = qualified_type->getAs<clang::TemplateSpecializationType>()) {
|
|
||||||
auto template_type_name = specialization->getTemplateName().getAsTemplateDecl()->getName();
|
|
||||||
if (template_type_name != "GCPtr" && template_type_name != "NonnullGCPtr" && template_type_name != "RawGCPtr")
|
|
||||||
return result;
|
|
||||||
|
|
||||||
auto const template_args = specialization->template_arguments();
|
|
||||||
if (template_args.size() != 1)
|
|
||||||
return result; // Not really valid, but will produce a compilation error anyway
|
|
||||||
|
|
||||||
auto const& type_arg = template_args[0];
|
|
||||||
auto const* record_type = type_arg.getAsType()->getAs<clang::RecordType>();
|
|
||||||
if (!record_type)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
auto const* record_decl = record_type->getAsCXXRecordDecl();
|
|
||||||
if (!record_decl->hasDefinition())
|
|
||||||
return result;
|
|
||||||
|
|
||||||
result.is_wrapped_in_gcptr = true;
|
|
||||||
result.is_valid = record_inherits_from_cell(*record_decl);
|
|
||||||
result.needs_visiting = template_type_name != "RawGCPtr";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LibJSGCVisitor::VisitCXXRecordDecl(clang::CXXRecordDecl* record)
|
bool LibJSGCVisitor::VisitCXXRecordDecl(clang::CXXRecordDecl* record)
|
||||||
@ -151,17 +170,20 @@ bool LibJSGCVisitor::VisitCXXRecordDecl(clang::CXXRecordDecl* record)
|
|||||||
|
|
||||||
auto& diag_engine = m_context.getDiagnostics();
|
auto& diag_engine = m_context.getDiagnostics();
|
||||||
std::vector<clang::FieldDecl const*> fields_that_need_visiting;
|
std::vector<clang::FieldDecl const*> fields_that_need_visiting;
|
||||||
|
auto record_is_cell = record_inherits_from_cell(*record);
|
||||||
|
|
||||||
for (clang::FieldDecl const* field : record->fields()) {
|
for (clang::FieldDecl const* field : record->fields()) {
|
||||||
auto validation_results = validate_field(field);
|
auto validation_results = validate_field_qualified_type(field);
|
||||||
if (!validation_results.is_valid) {
|
if (!validation_results)
|
||||||
if (validation_results.is_wrapped_in_gcptr) {
|
continue;
|
||||||
auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Error, "Specialization type must inherit from JS::Cell");
|
|
||||||
diag_engine.Report(field->getLocation(), diag_id);
|
auto [outer_type, base_type_inherits_from_cell] = *validation_results;
|
||||||
} else {
|
|
||||||
|
if (outer_type == OuterType::Ptr || outer_type == OuterType::Ref) {
|
||||||
|
if (base_type_inherits_from_cell) {
|
||||||
auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Error, "%0 to JS::Cell type should be wrapped in %1");
|
auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Error, "%0 to JS::Cell type should be wrapped in %1");
|
||||||
auto builder = diag_engine.Report(field->getLocation(), diag_id);
|
auto builder = diag_engine.Report(field->getLocation(), diag_id);
|
||||||
if (field->getType()->isReferenceType()) {
|
if (outer_type == OuterType::Ref) {
|
||||||
builder << "reference"
|
builder << "reference"
|
||||||
<< "JS::NonnullGCPtr";
|
<< "JS::NonnullGCPtr";
|
||||||
} else {
|
} else {
|
||||||
@ -169,12 +191,24 @@ bool LibJSGCVisitor::VisitCXXRecordDecl(clang::CXXRecordDecl* record)
|
|||||||
<< "JS::GCPtr";
|
<< "JS::GCPtr";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (validation_results.needs_visiting) {
|
} else if (outer_type == OuterType::GCPtr || outer_type == OuterType::RawGCPtr) {
|
||||||
|
if (!base_type_inherits_from_cell) {
|
||||||
|
auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Error, "Specialization type must inherit from JS::Cell");
|
||||||
|
diag_engine.Report(field->getLocation(), diag_id);
|
||||||
|
} else if (outer_type == OuterType::GCPtr) {
|
||||||
fields_that_need_visiting.push_back(field);
|
fields_that_need_visiting.push_back(field);
|
||||||
}
|
}
|
||||||
|
} else if (outer_type == OuterType::Handle || outer_type == OuterType::SafeFunction) {
|
||||||
|
if (record_is_cell && m_detect_invalid_function_members) {
|
||||||
|
// FIXME: Change this to an Error when all of the use cases get addressed and remove the plugin argument
|
||||||
|
auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "Types inheriting from JS::Cell should not have %0 fields");
|
||||||
|
auto builder = diag_engine.Report(field->getLocation(), diag_id);
|
||||||
|
builder << (outer_type == OuterType::Handle ? "JS::Handle" : "JS::SafeFunction");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!record_inherits_from_cell(*record))
|
if (!record_is_cell)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
validate_record_macros(*record);
|
validate_record_macros(*record);
|
||||||
@ -406,8 +440,9 @@ void LibJSGCVisitor::validate_record_macros(clang::CXXRecordDecl const& record)
|
|||||||
report_missing_macro();
|
report_missing_macro();
|
||||||
}
|
}
|
||||||
|
|
||||||
LibJSGCASTConsumer::LibJSGCASTConsumer(clang::CompilerInstance& compiler)
|
LibJSGCASTConsumer::LibJSGCASTConsumer(clang::CompilerInstance& compiler, bool detect_invalid_function_members)
|
||||||
: m_compiler(compiler)
|
: m_compiler(compiler)
|
||||||
|
, m_detect_invalid_function_members(detect_invalid_function_members)
|
||||||
{
|
{
|
||||||
auto& preprocessor = compiler.getPreprocessor();
|
auto& preprocessor = compiler.getPreprocessor();
|
||||||
preprocessor.addPPCallbacks(std::make_unique<LibJSPPCallbacks>(preprocessor, m_macro_map));
|
preprocessor.addPPCallbacks(std::make_unique<LibJSPPCallbacks>(preprocessor, m_macro_map));
|
||||||
@ -415,7 +450,7 @@ LibJSGCASTConsumer::LibJSGCASTConsumer(clang::CompilerInstance& compiler)
|
|||||||
|
|
||||||
void LibJSGCASTConsumer::HandleTranslationUnit(clang::ASTContext& context)
|
void LibJSGCASTConsumer::HandleTranslationUnit(clang::ASTContext& context)
|
||||||
{
|
{
|
||||||
LibJSGCVisitor visitor { context, m_macro_map };
|
LibJSGCVisitor visitor { context, m_macro_map, m_detect_invalid_function_members };
|
||||||
visitor.TraverseDecl(context.getTranslationUnitDecl());
|
visitor.TraverseDecl(context.getTranslationUnitDecl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,9 +53,10 @@ private:
|
|||||||
|
|
||||||
class LibJSGCVisitor : public clang::RecursiveASTVisitor<LibJSGCVisitor> {
|
class LibJSGCVisitor : public clang::RecursiveASTVisitor<LibJSGCVisitor> {
|
||||||
public:
|
public:
|
||||||
explicit LibJSGCVisitor(clang::ASTContext& context, LibJSCellMacroMap const& macro_map)
|
explicit LibJSGCVisitor(clang::ASTContext& context, LibJSCellMacroMap const& macro_map, bool detect_invalid_function_members)
|
||||||
: m_context(context)
|
: m_context(context)
|
||||||
, m_macro_map(macro_map)
|
, m_macro_map(macro_map)
|
||||||
|
, m_detect_invalid_function_members(detect_invalid_function_members)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,33 +73,39 @@ private:
|
|||||||
|
|
||||||
clang::ASTContext& m_context;
|
clang::ASTContext& m_context;
|
||||||
LibJSCellMacroMap const& m_macro_map;
|
LibJSCellMacroMap const& m_macro_map;
|
||||||
|
bool m_detect_invalid_function_members;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LibJSGCASTConsumer : public clang::ASTConsumer {
|
class LibJSGCASTConsumer : public clang::ASTConsumer {
|
||||||
public:
|
public:
|
||||||
explicit LibJSGCASTConsumer(clang::CompilerInstance&);
|
LibJSGCASTConsumer(clang::CompilerInstance&, bool detect_invalid_function_members);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual void HandleTranslationUnit(clang::ASTContext& context) override;
|
virtual void HandleTranslationUnit(clang::ASTContext& context) override;
|
||||||
|
|
||||||
clang::CompilerInstance& m_compiler;
|
clang::CompilerInstance& m_compiler;
|
||||||
LibJSCellMacroMap m_macro_map;
|
LibJSCellMacroMap m_macro_map;
|
||||||
|
bool m_detect_invalid_function_members;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LibJSGCPluginAction : public clang::PluginASTAction {
|
class LibJSGCPluginAction : public clang::PluginASTAction {
|
||||||
public:
|
public:
|
||||||
virtual bool ParseArgs(clang::CompilerInstance const&, std::vector<std::string> const&) override
|
virtual bool ParseArgs(clang::CompilerInstance const&, std::vector<std::string> const& args) override
|
||||||
{
|
{
|
||||||
|
m_detect_invalid_function_members = std::find(args.begin(), args.end(), "detect-invalid-function-members") != args.end();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance& compiler, llvm::StringRef) override
|
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance& compiler, llvm::StringRef) override
|
||||||
{
|
{
|
||||||
return std::make_unique<LibJSGCASTConsumer>(compiler);
|
return std::make_unique<LibJSGCASTConsumer>(compiler, m_detect_invalid_function_members);
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionType getActionType() override
|
ActionType getActionType() override
|
||||||
{
|
{
|
||||||
return AddAfterMainAction;
|
return AddAfterMainAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_detect_invalid_function_members { false };
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,9 @@ find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
|||||||
get_property(CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS GLOBAL PROPERTY CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS)
|
get_property(CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS GLOBAL PROPERTY CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS)
|
||||||
list(APPEND CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS -std=c++23 -Wno-user-defined-literals -Wno-literal-range)
|
list(APPEND CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS -std=c++23 -Wno-user-defined-literals -Wno-literal-range)
|
||||||
|
|
||||||
|
# Ensure we always check for invalid function field types regardless of the value of ENABLE_CLANG_PLUGINS_INVALID_FUNCTION_MEMBERS
|
||||||
|
list(APPEND CLANG_PLUGINS_COMPILE_OPTIONS_FOR_TESTS -fplugin-arg-libjs_gc_scanner-detect-invalid-function-members)
|
||||||
|
|
||||||
get_property(CLANG_PLUGINS_INCLUDE_DIRECTORIES TARGET AK PROPERTY INCLUDE_DIRECTORIES)
|
get_property(CLANG_PLUGINS_INCLUDE_DIRECTORIES TARGET AK PROPERTY INCLUDE_DIRECTORIES)
|
||||||
list(APPEND CLANG_PLUGINS_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
list(APPEND CLANG_PLUGINS_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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/Cell.h>
|
||||||
|
#include <LibJS/Heap/Handle.h>
|
||||||
|
#include <LibJS/SafeFunction.h>
|
||||||
|
|
||||||
|
class CellClass : JS::Cell {
|
||||||
|
JS_CELL(CellClass, JS::Cell);
|
||||||
|
|
||||||
|
// expected-warning@+1 {{Types inheriting from JS::Cell should not have JS::SafeFunction fields}}
|
||||||
|
JS::SafeFunction<void()> m_func;
|
||||||
|
|
||||||
|
// expected-warning@+1 {{Types inheriting from JS::Cell should not have JS::Handle fields}}
|
||||||
|
JS::Handle<JS::Cell> m_handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NonCellClass {
|
||||||
|
JS::SafeFunction<void()> m_func;
|
||||||
|
JS::Handle<JS::Cell> m_handle;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user