diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/.gitignore b/Meta/Lagom/Tools/LibJSGCVerifier/.gitignore deleted file mode 100644 index 308f63987ac..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build/ -cmake-build-debug/ -venv/ diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/CMakeLists.txt b/Meta/Lagom/Tools/LibJSGCVerifier/CMakeLists.txt deleted file mode 100644 index 7c6c55d6d19..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.24) - -project(LibJSGCVerifier C CXX) - -set(LAGOM_BUILD OFF CACHE BOOL "Build without the serenity toolchain Clang") - -if (LAGOM_BUILD) - find_package(Clang 17 CONFIG REQUIRED) -else() - find_package(Clang CONFIG REQUIRED HINTS "../../../../Toolchain/Local/clang") -endif() - -add_executable(LibJSGCVerifier src/main.cpp src/CellsHandler.cpp) - -target_include_directories(LibJSGCVerifier SYSTEM PRIVATE ${CLANG_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS}) -target_compile_features(LibJSGCVerifier PRIVATE cxx_std_20) -target_link_libraries(LibJSGCVerifier PRIVATE clangTooling) - -target_compile_options(LibJSGCVerifier PRIVATE - -Wall - -Wextra - -Werror - -Wno-unused - -fno-rtti -) diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/README.md b/Meta/Lagom/Tools/LibJSGCVerifier/README.md deleted file mode 100644 index 5568e87675b..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/README.md +++ /dev/null @@ -1,44 +0,0 @@ -### LibJSGCVerifier - -This is a simple Clang tool to validate certain behavior relating to LibJS's GC. It currently validates -two things: - -- For all types wrapped by `GCPtr` or `NonnullGCPtr`, that the wrapped type inherits from `Cell` -- For all types not wrapped by `GCPtr` or `NonnullGCPtr`, that the wrapped type does not inherit from `Cell` - (otherwise it should be wrapped). - -This tool currently support being built with the Serenity Clang toolchain or the lagom Clang toolchain (in which case it won't be able to verify Serenity-only applications) - -#### Building & Running with the Serenity toolchain -First build Serenity with the Clang toolchain for x86_64: -```bash -./Meta/serenity.sh build x86_64 Clang -``` - -Then build the tool with: -```bash -cmake -GNinja -B build -cmake --build build -``` - -Then run the tool with: -```bash -src/main.py -b /Build -``` - -#### Building & Running with the Lagom toolchain -First build the Serenity lagom applications with: -```bash -./Meta/serenity.sh build lagom -``` - -Then build the tool with: -```bash -cmake -GNinja -DLAGOM_BUILD=ON -B build -cmake --build build -``` - -Then run the tool with: -```bash -src/main.py -l -b /Build -``` diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/src/CellsHandler.cpp b/Meta/Lagom/Tools/LibJSGCVerifier/src/CellsHandler.cpp deleted file mode 100644 index 2f7968c93f9..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/src/CellsHandler.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright (c) 2023, Matthew Olsson - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "CellsHandler.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -AST_MATCHER_P(clang::Decl, hasAnnotation, std::string, name) -{ - (void)Builder; - (void)Finder; - for (auto const* attr : Node.attrs()) { - if (auto const* annotate_attr = llvm::dyn_cast(attr)) { - if (annotate_attr->getAnnotation() == name) - return true; - } - } - return false; -} - -template -class SimpleCollectMatchesCallback : public clang::ast_matchers::MatchFinder::MatchCallback { -public: - explicit SimpleCollectMatchesCallback(std::string name) - : m_name(std::move(name)) - { - } - - void run(clang::ast_matchers::MatchFinder::MatchResult const& result) override - { - if (auto const* node = result.Nodes.getNodeAs(m_name)) - m_matches.push_back(node); - } - - auto const& matches() const { return m_matches; } - -private: - std::string m_name; - std::vector m_matches; -}; - -CollectCellsHandler::CollectCellsHandler() -{ - using namespace clang::ast_matchers; - - m_finder.addMatcher( - traverse( - clang::TK_IgnoreUnlessSpelledInSource, - cxxRecordDecl(decl().bind("record-decl"))), - this); - - auto non_capturable_var_decl = varDecl( - hasLocalStorage(), - unless( - anyOf( - // The declaration has an annotation: - // IGNORE_USE_IN_ESCAPING_LAMBDA Foo foo; - hasAnnotation("serenity::ignore_use_in_escaping_lambda"), - // The declaration is a reference: - // Foo& foo_ref = get_foo_ref(); - // Foo* foo_ptr = get_foo_ptr(); - // do_something([&foo_ref, &foo_ptr] { - // foo_ref.foo(); // Fine, foo_ref references the underlying Foo instance - // foo_ptr->foo(); // Bad, foo_ptr references the pointer on the stack above - // }); - hasType(references(TypeMatcher(anything())))))); - - auto bad_lambda_capture = lambdaCapture(anyOf(capturesThis(), capturesVar(non_capturable_var_decl))).bind("lambda-capture"); - - auto lambda_with_bad_capture = lambdaExpr( - anyOf( - // These are both required as they have slightly different behavior. - // - // We need forEachLambdaCapture because we need to go over every explicit capture in the capture list, as - // hasAnyCapture will just take the first capture in the list that matches the criteria (usually the `this` - // capture). Without it, if the first capture in the list was flagged as bad but is actually fine (e.g. the - // `this` capture, or a var capture by value), but there was a second capture in the list that was invalid, - // it would be skipped. - // - // But forEachLambdaCapture doesn't seem to find implicit captures, so we also need hasAnyCapture to handle - // captures that aren't explicitly listed in the capture list, but are still invalid. - forEachLambdaCapture(bad_lambda_capture), - hasAnyCapture(bad_lambda_capture))); - - // Bind this varDecl so we can reference it later to make sure it isn't being called - auto lambda_with_bad_capture_decl = varDecl(hasInitializer(lambda_with_bad_capture)).bind("lambda"); - - m_finder.addMatcher( - traverse( - clang::TK_IgnoreUnlessSpelledInSource, - callExpr( - forEachArgumentWithParam( - anyOf( - // Match a lambda given directly in the function call - lambda_with_bad_capture, - // Matches an expression with a possibly-deeply-nested reference to a variable with a lambda type, e.g: - // auto lambda = [...] { ... }; - // some_func(move(lambda)); - has(declRefExpr( - to(lambda_with_bad_capture_decl), - // Avoid immediately invoked lambdas (i.e. match `move(lambda)` but not `move(lambda())`) - unless(hasParent( - // ::operator()(...) - cxxOperatorCallExpr(has(declRefExpr(to(equalsBoundNode("lambda")))))))))), - parmVarDecl( - allOf( - // It's important that the parameter has a RecordType, as a templated type can never escape its function - hasType(cxxRecordDecl()), - unless(hasAnnotation("serenity::noescape")))) - .bind("lambda-param-ref")))), - this); -} - -bool record_inherits_from_cell(clang::CXXRecordDecl const& record) -{ - if (!record.isCompleteDefinition()) - return false; - - bool inherits_from_cell = record.getQualifiedNameAsString() == "JS::Cell"; - record.forallBases([&](clang::CXXRecordDecl const* base) -> bool { - if (base->getQualifiedNameAsString() == "JS::Cell") { - inherits_from_cell = true; - return false; - } - return true; - }); - return inherits_from_cell; -} - -std::vector get_all_qualified_types(clang::QualType const& type) -{ - std::vector qualified_types; - - if (auto const* template_specialization = type->getAs()) { - auto specialization_name = template_specialization->getTemplateName().getAsTemplateDecl()->getQualifiedNameAsString(); - // Do not unwrap GCPtr/NonnullGCPtr/MarkedVector - if (specialization_name == "JS::GCPtr" || specialization_name == "JS::NonnullGCPtr" || specialization_name == "JS::RawGCPtr" || specialization_name == "JS::MarkedVector") { - qualified_types.push_back(type); - } else { - auto const template_arguments = template_specialization->template_arguments(); - for (size_t i = 0; i < template_arguments.size(); i++) { - auto const& template_arg = template_arguments[i]; - if (template_arg.getKind() == clang::TemplateArgument::Type) { - auto template_qualified_types = get_all_qualified_types(template_arg.getAsType()); - std::move(template_qualified_types.begin(), template_qualified_types.end(), std::back_inserter(qualified_types)); - } - } - } - } else { - qualified_types.push_back(type); - } - - return qualified_types; -} - -struct FieldValidationResult { - bool is_valid { false }; - bool is_wrapped_in_gcptr { false }; - bool needs_visiting { false }; -}; - -FieldValidationResult validate_field(clang::FieldDecl const* field_decl) -{ - auto type = field_decl->getType(); - if (auto const* elaborated_type = llvm::dyn_cast(type.getTypePtr())) - type = elaborated_type->desugar(); - - FieldValidationResult result { .is_valid = true }; - - for (auto const& qualified_type : get_all_qualified_types(type)) { - if (auto const* pointer_decl = qualified_type->getAs()) { - if (auto const* pointee = pointer_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* reference_decl = qualified_type->getAs()) { - 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()) { - 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(); - 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"; - } else if (auto const* record = qualified_type->getAsCXXRecordDecl()) { - if (record->getQualifiedNameAsString() == "JS::Value") { - result.needs_visiting = true; - } - } - } - - return result; -} - -void emit_record_json_data(clang::CXXRecordDecl const& record) -{ - llvm::json::Object obj; - obj.insert({ "name", record.getQualifiedNameAsString() }); - - std::vector bases; - record.forallBases([&](clang::CXXRecordDecl const* base) { - bases.push_back(base->getQualifiedNameAsString()); - return true; - }); - obj.insert({ "parents", bases }); - - bool has_cell_allocator = false; - bool has_js_constructor = false; - for (auto const& decl : record.decls()) { - if (auto* var_decl = llvm::dyn_cast(decl); var_decl && var_decl->getQualifiedNameAsString().ends_with("::cell_allocator")) { - has_cell_allocator = true; - } else if (auto* fn_decl = llvm::dyn_cast(decl); fn_decl && fn_decl->getQualifiedNameAsString().ends_with("::construct_impl")) { - has_js_constructor = true; - } - } - obj.insert({ "has_cell_allocator", has_cell_allocator }); - obj.insert({ "has_js_constructor", has_js_constructor }); - - llvm::outs() << std::move(obj) << "\n"; -} - -void CollectCellsHandler::run(clang::ast_matchers::MatchFinder::MatchResult const& result) -{ - check_cells(result); - check_lambda_captures(result); -} - -void CollectCellsHandler::check_cells(clang::ast_matchers::MatchFinder::MatchResult const& result) -{ - using namespace clang::ast_matchers; - - clang::CXXRecordDecl const* record = result.Nodes.getNodeAs("record-decl"); - if (!record || !record->isCompleteDefinition() || (!record->isClass() && !record->isStruct())) - return; - - // Cell triggers a bunch of warnings for its empty visit_edges implementation, but - // it doesn't have any members anyways so it's fine to just ignore. - auto qualified_name = record->getQualifiedNameAsString(); - if (qualified_name == "JS::Cell") - return; - - auto& diag_engine = result.Context->getDiagnostics(); - std::vector fields_that_need_visiting; - - for (clang::FieldDecl const* field : record->fields()) { - auto validation_results = validate_field(field); - if (!validation_results.is_valid) { - if (validation_results.is_wrapped_in_gcptr) { - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "Specialization type must inherit from JS::Cell"); - diag_engine.Report(field->getLocation(), diag_id); - } else { - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "%0 to JS::Cell type should be wrapped in %1"); - auto builder = diag_engine.Report(field->getLocation(), diag_id); - if (field->getType()->isReferenceType()) { - builder << "reference" - << "JS::NonnullGCPtr"; - } else { - builder << "pointer" - << "JS::GCPtr"; - } - } - } else if (validation_results.needs_visiting) { - fields_that_need_visiting.push_back(field); - } - } - - if (!record_inherits_from_cell(*record)) - return; - - emit_record_json_data(*record); - - bool has_base = false; - for (auto const& decl : record->decls()) { - if (auto* alias_decl = llvm::dyn_cast(decl); alias_decl && alias_decl->getQualifiedNameAsString().ends_with("::Base")) { - has_base = true; - break; - } - } - - if (!has_base) { - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "JS::Cell-inheriting class %0 is missing a JS_CELL() call in its header file"); - auto builder = diag_engine.Report(record->getLocation(), diag_id); - builder << record->getName(); - } - - clang::DeclarationName const name = &result.Context->Idents.get("visit_edges"); - auto const* visit_edges_method = record->lookup(name).find_first(); - if (!visit_edges_method && !fields_that_need_visiting.empty()) { - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "JS::Cell-inheriting class %0 contains a GC-allocated member %1 but has no visit_edges method"); - auto builder = diag_engine.Report(record->getLocation(), diag_id); - builder << record->getName() - << fields_that_need_visiting[0]; - } - if (!visit_edges_method || !visit_edges_method->getBody()) - return; - - // Search for a call to Base::visit_edges. Note that this also has the nice side effect of - // ensuring the classes use JS_CELL/JS_OBJECT, as Base will not be defined if they do not. - - MatchFinder base_visit_edges_finder; - SimpleCollectMatchesCallback base_visit_edges_callback("member-call"); - - auto base_visit_edges_matcher = cxxMethodDecl( - ofClass(hasName(qualified_name)), - functionDecl(hasName("visit_edges")), - isOverride(), - hasDescendant(memberExpr(member(hasName("visit_edges"))).bind("member-call"))); - - base_visit_edges_finder.addMatcher(base_visit_edges_matcher, &base_visit_edges_callback); - base_visit_edges_finder.matchAST(*result.Context); - - bool call_to_base_visit_edges_found = false; - - for (auto const* call_expr : base_visit_edges_callback.matches()) { - // FIXME: Can we constrain the matcher above to avoid looking directly at the source code? - auto const* source_chars = result.SourceManager->getCharacterData(call_expr->getBeginLoc()); - if (strncmp(source_chars, "Base::", 6) == 0) { - call_to_base_visit_edges_found = true; - break; - } - } - - if (!call_to_base_visit_edges_found) { - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "Missing call to Base::visit_edges"); - diag_engine.Report(visit_edges_method->getBeginLoc(), diag_id); - } - - // Search for uses of all fields that need visiting. We don't ensure they are _actually_ visited - // with a call to visitor.visit(...), as that is too complex. Instead, we just assume that if the - // field is accessed at all, then it is visited. - - if (fields_that_need_visiting.empty()) - return; - - MatchFinder field_access_finder; - SimpleCollectMatchesCallback field_access_callback("member-expr"); - - auto field_access_matcher = memberExpr( - hasAncestor(cxxMethodDecl(hasName("visit_edges"))), - hasObjectExpression(hasType(pointsTo(cxxRecordDecl(hasName(record->getName())))))) - .bind("member-expr"); - - field_access_finder.addMatcher(field_access_matcher, &field_access_callback); - field_access_finder.matchAST(visit_edges_method->getASTContext()); - - std::unordered_set fields_that_are_visited; - for (auto const* member_expr : field_access_callback.matches()) - fields_that_are_visited.insert(member_expr->getMemberNameInfo().getAsString()); - - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "GC-allocated member is not visited in %0::visit_edges"); - - for (auto const* field : fields_that_need_visiting) { - if (!fields_that_are_visited.contains(field->getNameAsString())) { - auto builder = diag_engine.Report(field->getBeginLoc(), diag_id); - builder << record->getName(); - } - } -} - -void CollectCellsHandler::check_lambda_captures(clang::ast_matchers::MatchFinder::MatchResult const& result) -{ - auto& diag_engine = result.Context->getDiagnostics(); - - if (auto const* capture = result.Nodes.getNodeAs("lambda-capture")) { - if (capture->capturesThis() || capture->getCaptureKind() != clang::LCK_ByRef) - return; - - auto diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Warning, "Variable with local storage is captured by reference in a lambda that may be asynchronously executed"); - diag_engine.Report(capture->getLocation(), diag_id); - - clang::SourceLocation captured_var_location; - if (auto const* var_decl = llvm::dyn_cast(capture->getCapturedVar())) { - captured_var_location = var_decl->getTypeSourceInfo()->getTypeLoc().getBeginLoc(); - } else { - captured_var_location = capture->getCapturedVar()->getLocation(); - } - diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Note, "Annotate the variable declaration with IGNORE_USE_IN_ESCAPING_LAMBDA if it outlives the lambda"); - diag_engine.Report(captured_var_location, diag_id); - - auto const* param = result.Nodes.getNodeAs("lambda-param-ref"); - diag_id = diag_engine.getCustomDiagID(clang::DiagnosticsEngine::Note, "Annotate the parameter with NOESCAPE if the lambda will not outlive the function call"); - diag_engine.Report(param->getTypeSourceInfo()->getTypeLoc().getBeginLoc(), diag_id); - } -} diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/src/CellsHandler.h b/Meta/Lagom/Tools/LibJSGCVerifier/src/CellsHandler.h deleted file mode 100644 index fdf2be2fd39..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/src/CellsHandler.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023, Matthew Olsson - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -class CollectCellsHandler - : public clang::tooling::SourceFileCallbacks - , public clang::ast_matchers::MatchFinder::MatchCallback { -public: - CollectCellsHandler(); - virtual ~CollectCellsHandler() override = default; - - virtual void run(clang::ast_matchers::MatchFinder::MatchResult const& result) override; - - clang::ast_matchers::MatchFinder& finder() { return m_finder; } - -private: - void check_cells(clang::ast_matchers::MatchFinder::MatchResult const& result); - void check_lambda_captures(clang::ast_matchers::MatchFinder::MatchResult const& result); - - std::unordered_set m_visited_classes; - clang::ast_matchers::MatchFinder m_finder; -}; diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/src/main.cpp b/Meta/Lagom/Tools/LibJSGCVerifier/src/main.cpp deleted file mode 100644 index df6d5512f78..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/src/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2023, Matthew Olsson - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "CellsHandler.h" -#include -#include -#include - -int main(int argc, char const** argv) -{ - llvm::cl::OptionCategory s_tool_category("LibJSGCVerifier options"); - auto maybe_parser = clang::tooling::CommonOptionsParser::create(argc, argv, s_tool_category); - if (!maybe_parser) { - llvm::errs() << maybe_parser.takeError(); - return 1; - } - - auto& parser = maybe_parser.get(); - clang::tooling::ClangTool tool(parser.getCompilations(), parser.getSourcePathList()); - - CollectCellsHandler collect_handler; - auto collect_action = clang::tooling::newFrontendActionFactory(&collect_handler.finder(), &collect_handler); - return tool.run(collect_action.get()); -} diff --git a/Meta/Lagom/Tools/LibJSGCVerifier/src/main.py b/Meta/Lagom/Tools/LibJSGCVerifier/src/main.py deleted file mode 100755 index 9a1f8d38ed1..00000000000 --- a/Meta/Lagom/Tools/LibJSGCVerifier/src/main.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import json -import multiprocessing -import os -from pathlib import Path -import signal -import subprocess -import sys - -# Relative to Userland directory -COMMON_PATHS_TO_SEARCH = [ - 'Libraries/LibJS', - 'Libraries/LibMarkdown', - 'Libraries/LibWeb', - 'Services/WebContent', - 'Services/WebWorker', -] -SERENITY_PATHS_TO_SEARCH = COMMON_PATHS_TO_SEARCH + [ - 'Applications/Assistant', - 'Applications/Browser', - 'Applications/Spreadsheet', - 'Applications/TextEditor', - 'DevTools/HackStudio', -] - -parser = argparse.ArgumentParser('LibJSGCVerifier', description='A Clang tool to validate usage of the LibJS GC') -parser.add_argument('-b', '--build-path', required=True, help='Path to the project Build folder') -parser.add_argument('-l', '--lagom', action='store_true', required=False, - help='Use the lagom build instead of the serenity build') -args = parser.parse_args() - -build_path = Path(args.build_path).resolve() -userland_path = build_path / '..' / 'Userland' -if args.lagom: - compile_commands_path = build_path / 'lagom' / 'compile_commands.json' -else: - compile_commands_path = build_path / 'x86_64clang' / 'compile_commands.json' - -if not compile_commands_path.exists(): - print(f'Could not find compile_commands.json in {compile_commands_path.parent}') - exit(1) - -paths = [] - -if args.lagom: - paths_to_search = COMMON_PATHS_TO_SEARCH -else: - paths_to_search = SERENITY_PATHS_TO_SEARCH -for containing_path in paths_to_search: - for root, dirs, files in os.walk(userland_path / containing_path): - for file in files: - if file.endswith('.cpp'): - paths.append(Path(root) / file) - - -def thread_init(): - signal.signal(signal.SIGINT, signal.SIG_IGN) - - -def thread_execute(file_path): - clang_args = [ - './build/LibJSGCVerifier', - '--extra-arg', - '-DUSING_AK_GLOBALLY=1', # To avoid errors about USING_AK_GLOBALLY not being defined at all - '--extra-arg', - '-DNULL=0', # To avoid errors about NULL not being defined at all - '-p', - compile_commands_path, - file_path - ] - proc = subprocess.Popen(clang_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - print(f'Processed {file_path.resolve()}') - if stderr: - print(stderr.decode(), file=sys.stderr) - - results = [] - if stdout: - for line in stdout.split(b'\n'): - if line: - results.append(json.loads(line)) - return results - - -with multiprocessing.Pool(processes=multiprocessing.cpu_count() - 2, initializer=thread_init) as pool: - try: - clang_results = [] - for results in pool.map(thread_execute, paths): - clang_results.extend(results) - except KeyboardInterrupt: - pool.terminate() - pool.join() - -# Process output data -clang_results = {r['name']: r for r in clang_results} -leaf_objects = set(clang_results.keys()) -for result in clang_results.values(): - leaf_objects.difference_update(result['parents']) - -for key, value in clang_results.items(): - if key == 'JS::HeapBlock::FreelistEntry' or key == 'JS::HeapFunction': - # These are Heap-related classes and don't need their own allocator - continue - - if not value['has_cell_allocator'] and (key in leaf_objects or value['has_js_constructor']): - print(f'Class {key} is missing a JS_DECLARE_ALLOCATOR() declaration in its header file')