зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1865048 - Mozsearch indexer: add bindingSlots/slotOwner to C++ code bound from/to Java code r=asuth
This is a squash of commits 978bdb2d..549ce9bf from github:mozsearch/mozsearch. Previously reviewed on https://github.com/mozsearch/mozsearch/pull/673. Depends on D196795 Differential Revision: https://phabricator.services.mozilla.com/D196796
This commit is contained in:
Родитель
2c14fb7559
Коммит
4303f386e4
|
@ -60,6 +60,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
|
|||
|
||||
if CONFIG["ENABLE_MOZSEARCH_PLUGIN"]:
|
||||
HOST_SOURCES += [
|
||||
"mozsearch-plugin/BindingOperations.cpp",
|
||||
"mozsearch-plugin/FileOperations.cpp",
|
||||
"mozsearch-plugin/from-clangd/HeuristicResolver.cpp",
|
||||
"mozsearch-plugin/MozsearchIndexer.cpp",
|
||||
|
|
|
@ -0,0 +1,792 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BindingOperations.h"
|
||||
|
||||
#include <clang/AST/Attr.h>
|
||||
#include <clang/AST/Expr.h>
|
||||
#include <clang/AST/RecursiveASTVisitor.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cuchar>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __cpp_lib_optional
|
||||
#include <optional>
|
||||
template<typename T> using optional = std::optional<T>;
|
||||
#else
|
||||
#include <llvm/ADT/Optional.h>
|
||||
template<typename T> using optional = clang::Optional<T>;
|
||||
#endif
|
||||
|
||||
using namespace clang;
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename InputIt>
|
||||
bool hasReverseQualifiedName(InputIt first, InputIt last, const NamedDecl &tag)
|
||||
{
|
||||
const NamedDecl *currentDecl = &tag;
|
||||
InputIt currentName;
|
||||
for (currentName = first; currentName != last; currentName++) {
|
||||
if (!currentDecl || !currentDecl->getIdentifier() || currentDecl->getName() != *currentName)
|
||||
return false;
|
||||
|
||||
currentDecl = dyn_cast<NamedDecl>(currentDecl->getDeclContext());
|
||||
}
|
||||
if (currentName != last)
|
||||
return false;
|
||||
|
||||
if (currentDecl != nullptr)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isMozillaJniObjectBase(const CXXRecordDecl &klass)
|
||||
{
|
||||
const auto qualifiedName = std::array<StringRef, 3>{"mozilla", "jni", "ObjectBase"};
|
||||
return hasReverseQualifiedName(qualifiedName.crbegin(), qualifiedName.crend(), klass);
|
||||
}
|
||||
|
||||
bool isMozillaJniNativeImpl(const CXXRecordDecl &klass)
|
||||
{
|
||||
const auto qualifiedName = std::array<StringRef, 3>{"mozilla", "jni", "NativeImpl"};
|
||||
return hasReverseQualifiedName(qualifiedName.crbegin(), qualifiedName.crend(), klass);
|
||||
}
|
||||
|
||||
const NamedDecl *fieldNamed(StringRef name, const RecordDecl &strukt)
|
||||
{
|
||||
for (const auto *decl : strukt.decls()) {
|
||||
const auto *namedDecl = dyn_cast<VarDecl>(decl);
|
||||
if (!namedDecl)
|
||||
continue;
|
||||
|
||||
if (!namedDecl->getIdentifier() || namedDecl->getName() != name)
|
||||
continue;
|
||||
|
||||
return namedDecl;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
optional<StringRef> nameFieldValue(const RecordDecl &strukt)
|
||||
{
|
||||
const auto *nameField = dyn_cast_or_null<VarDecl>(fieldNamed("name", strukt));
|
||||
if (!nameField)
|
||||
return {};
|
||||
|
||||
const auto *def = nameField->getDefinition();
|
||||
if (!def)
|
||||
return {};
|
||||
|
||||
const auto *name = dyn_cast_or_null<StringLiteral>(def->getInit());
|
||||
if (!name)
|
||||
return {};
|
||||
|
||||
return name->getString();
|
||||
}
|
||||
|
||||
struct AbstractBinding {
|
||||
// Subset of tools/analysis/BindingSlotLang
|
||||
enum class Lang {
|
||||
Cpp,
|
||||
Jvm,
|
||||
};
|
||||
static constexpr size_t LangLength = 2;
|
||||
static constexpr std::array<StringRef, LangLength> langNames = {
|
||||
"cpp",
|
||||
"jvm",
|
||||
};
|
||||
|
||||
static optional<Lang> langFromString(StringRef langName)
|
||||
{
|
||||
const auto it = std::find(langNames.begin(), langNames.end(), langName);
|
||||
if (it == langNames.end())
|
||||
return {};
|
||||
|
||||
return Lang(it - langNames.begin());
|
||||
}
|
||||
static StringRef stringFromLang(Lang lang)
|
||||
{
|
||||
return langNames[size_t(lang)];
|
||||
}
|
||||
|
||||
// Subset of tools/analysis/BindingSlotKind
|
||||
enum class Kind {
|
||||
Class,
|
||||
Method,
|
||||
Getter,
|
||||
Setter,
|
||||
Const,
|
||||
};
|
||||
static constexpr size_t KindLength = 5;
|
||||
static constexpr std::array<StringRef, KindLength> kindNames = {
|
||||
"class",
|
||||
"method",
|
||||
"getter",
|
||||
"setter",
|
||||
"const",
|
||||
};
|
||||
|
||||
static optional<Kind> kindFromString(StringRef kindName)
|
||||
{
|
||||
const auto it = std::find(kindNames.begin(), kindNames.end(), kindName);
|
||||
if (it == kindNames.end())
|
||||
return {};
|
||||
|
||||
return Kind(it - kindNames.begin());
|
||||
}
|
||||
static StringRef stringFromKind(Kind kind)
|
||||
{
|
||||
return kindNames[size_t(kind)];
|
||||
}
|
||||
|
||||
Lang lang;
|
||||
Kind kind;
|
||||
StringRef symbol;
|
||||
};
|
||||
constexpr size_t AbstractBinding::KindLength;
|
||||
constexpr std::array<StringRef, AbstractBinding::KindLength> AbstractBinding::kindNames;
|
||||
constexpr size_t AbstractBinding::LangLength;
|
||||
constexpr std::array<StringRef, AbstractBinding::LangLength> AbstractBinding::langNames;
|
||||
|
||||
struct BindingTo : public AbstractBinding {
|
||||
BindingTo(AbstractBinding b) : AbstractBinding(std::move(b)) {}
|
||||
static constexpr StringRef ANNOTATION = "binding_to";
|
||||
};
|
||||
constexpr StringRef BindingTo::ANNOTATION;
|
||||
|
||||
struct BoundAs : public AbstractBinding {
|
||||
BoundAs(AbstractBinding b) : AbstractBinding(std::move(b)) {}
|
||||
static constexpr StringRef ANNOTATION = "bound_as";
|
||||
};
|
||||
constexpr StringRef BoundAs::ANNOTATION;
|
||||
|
||||
template<typename B>
|
||||
void setBindingAttr(ASTContext &C, Decl &decl, B binding)
|
||||
{
|
||||
// recent LLVM: CreateImplicit then setDelayedArgs
|
||||
Expr *langExpr = StringLiteral::Create(C, AbstractBinding::stringFromLang(binding.lang), StringLiteral::UTF8, false, {}, {});
|
||||
Expr *kindExpr = StringLiteral::Create(C, AbstractBinding::stringFromKind(binding.kind), StringLiteral::UTF8, false, {}, {});
|
||||
Expr *symbolExpr = StringLiteral::Create(C, binding.symbol, StringLiteral::UTF8, false, {}, {});
|
||||
auto **args = new (C, 16) Expr *[3]{langExpr, kindExpr, symbolExpr};
|
||||
auto *attr = AnnotateAttr::CreateImplicit(C, B::ANNOTATION, args, 3);
|
||||
decl.addAttr(attr);
|
||||
}
|
||||
|
||||
optional<AbstractBinding> readBinding(const AnnotateAttr &attr)
|
||||
{
|
||||
if (attr.args_size() != 3)
|
||||
return {};
|
||||
|
||||
const auto *langExpr = attr.args().begin()[0];
|
||||
const auto *kindExpr = attr.args().begin()[1];
|
||||
const auto *symbolExpr = attr.args().begin()[2];
|
||||
if (!langExpr || !kindExpr || !symbolExpr)
|
||||
return {};
|
||||
|
||||
const auto *langName = dyn_cast<StringLiteral>(langExpr->IgnoreUnlessSpelledInSource());
|
||||
const auto *kindName = dyn_cast<StringLiteral>(kindExpr->IgnoreUnlessSpelledInSource());
|
||||
const auto *symbol = dyn_cast<StringLiteral>(symbolExpr->IgnoreUnlessSpelledInSource());
|
||||
if (!langName || !kindName || !symbol)
|
||||
return {};
|
||||
|
||||
const auto lang = AbstractBinding::langFromString(langName->getString());
|
||||
const auto kind = AbstractBinding::kindFromString(kindName->getString());
|
||||
|
||||
if (!lang || !kind)
|
||||
return {};
|
||||
|
||||
return AbstractBinding {
|
||||
.lang = *lang,
|
||||
.kind = *kind,
|
||||
.symbol = symbol->getString(),
|
||||
};
|
||||
}
|
||||
|
||||
optional<BindingTo> getBindingTo(const Decl &decl)
|
||||
{
|
||||
for (const auto *attr : decl.specific_attrs<AnnotateAttr>()) {
|
||||
if (attr->getAnnotation() != BindingTo::ANNOTATION)
|
||||
continue;
|
||||
|
||||
const auto binding = readBinding(*attr);
|
||||
if (!binding)
|
||||
continue;
|
||||
|
||||
return BindingTo{*binding};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// C++23: turn into generator
|
||||
std::vector<BoundAs> getBoundAs(const Decl &decl)
|
||||
{
|
||||
std::vector<BoundAs> found;
|
||||
|
||||
for (const auto *attr : decl.specific_attrs<AnnotateAttr>()) {
|
||||
if (attr->getAnnotation() != BoundAs::ANNOTATION)
|
||||
continue;
|
||||
|
||||
const auto binding = readBinding(*attr);
|
||||
if (!binding)
|
||||
continue;
|
||||
|
||||
found.push_back(BoundAs{*binding});
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
class FindCallCall : private RecursiveASTVisitor<FindCallCall>
|
||||
{
|
||||
public:
|
||||
struct Result {
|
||||
using Kind = AbstractBinding::Kind;
|
||||
|
||||
Kind kind;
|
||||
StringRef name;
|
||||
};
|
||||
|
||||
static optional<Result> search(Stmt *statement)
|
||||
{
|
||||
FindCallCall finder;
|
||||
finder.TraverseStmt(statement);
|
||||
return finder.result;
|
||||
}
|
||||
|
||||
private:
|
||||
optional<Result> result;
|
||||
|
||||
friend RecursiveASTVisitor<FindCallCall>;
|
||||
|
||||
optional<Result> tryParseCallCall(CallExpr *callExpr){
|
||||
const auto *callee = dyn_cast_or_null<CXXMethodDecl>(callExpr->getDirectCallee());
|
||||
if (!callee)
|
||||
return {};
|
||||
|
||||
if (!callee->getIdentifier())
|
||||
return {};
|
||||
|
||||
const auto action = callee->getIdentifier()->getName();
|
||||
|
||||
if (action != "Call" && action != "Get" && action != "Set")
|
||||
return {};
|
||||
|
||||
const auto *parentClass = dyn_cast_or_null<ClassTemplateSpecializationDecl>(callee->getParent());
|
||||
|
||||
if (!parentClass)
|
||||
return {};
|
||||
|
||||
const auto *parentTemplate = parentClass->getTemplateInstantiationPattern();
|
||||
|
||||
if (!parentTemplate || !parentTemplate->getIdentifier())
|
||||
return {};
|
||||
|
||||
const auto parentName = parentTemplate->getIdentifier()->getName();
|
||||
|
||||
AbstractBinding::Kind kind;
|
||||
if (action == "Call") {
|
||||
if (parentName == "Constructor" || parentName == "Method") {
|
||||
kind = AbstractBinding::Kind::Method;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else if (parentName == "Field") {
|
||||
if (action == "Get") {
|
||||
kind = AbstractBinding::Kind::Getter;
|
||||
} else if (action == "Set") {
|
||||
kind = AbstractBinding::Kind::Setter;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto *templateArg = parentClass->getTemplateArgs().get(0).getAsType()->getAsRecordDecl();
|
||||
|
||||
if (!templateArg)
|
||||
return {};
|
||||
|
||||
const auto name = nameFieldValue(*templateArg);
|
||||
if (!name)
|
||||
return {};
|
||||
|
||||
return Result {
|
||||
.kind = kind,
|
||||
.name = *name,
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
bool VisitCallExpr(CallExpr *callExpr)
|
||||
{
|
||||
return !(result = tryParseCallCall(callExpr));
|
||||
}
|
||||
};
|
||||
|
||||
constexpr StringRef JVM_SCIP_SYMBOL_PREFIX = "S_jvm_";
|
||||
|
||||
std::string javaScipSymbol(StringRef prefix, StringRef name, AbstractBinding::Kind kind)
|
||||
{
|
||||
auto symbol = (prefix + name).str();
|
||||
|
||||
switch (kind) {
|
||||
case AbstractBinding::Kind::Class:
|
||||
std::replace(symbol.begin(), symbol.end(), '$', '#');
|
||||
symbol += "#";
|
||||
break;
|
||||
case AbstractBinding::Kind::Method:
|
||||
symbol += "().";
|
||||
break;
|
||||
case AbstractBinding::Kind::Const:
|
||||
case AbstractBinding::Kind::Getter:
|
||||
case AbstractBinding::Kind::Setter:
|
||||
symbol += ".";
|
||||
break;
|
||||
}
|
||||
|
||||
return symbol;
|
||||
}
|
||||
|
||||
void addSlotOwnerAttribute(llvm::json::OStream &J, const Decl &decl)
|
||||
{
|
||||
if (const auto bindingTo = getBindingTo(decl)) {
|
||||
J.attributeBegin("slotOwner");
|
||||
J.objectBegin();
|
||||
J.attribute("slotKind", AbstractBinding::stringFromKind(bindingTo->kind));
|
||||
J.attribute("slotLang", "cpp");
|
||||
J.attribute("ownerLang", AbstractBinding::stringFromLang(bindingTo->lang));
|
||||
J.attribute("sym", bindingTo->symbol);
|
||||
J.objectEnd();
|
||||
J.attributeEnd();
|
||||
}
|
||||
}
|
||||
void addBindingSlotsAttribute(llvm::json::OStream &J, const Decl &decl)
|
||||
{
|
||||
const auto allBoundAs = getBoundAs(decl);
|
||||
if (!allBoundAs.empty()) {
|
||||
J.attributeBegin("bindingSlots");
|
||||
J.arrayBegin();
|
||||
for (const auto boundAs : allBoundAs) {
|
||||
J.objectBegin();
|
||||
J.attribute("slotKind", AbstractBinding::stringFromKind(boundAs.kind));
|
||||
J.attribute("slotLang", AbstractBinding::stringFromLang(boundAs.lang));
|
||||
J.attribute("ownerLang", "cpp");
|
||||
J.attribute("sym", boundAs.symbol);
|
||||
J.objectEnd();
|
||||
}
|
||||
J.arrayEnd();
|
||||
J.attributeEnd();
|
||||
}
|
||||
}
|
||||
|
||||
// The mangling scheme is documented at https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html
|
||||
// The main takeaways are:
|
||||
// - _0xxxx is the utf16 code unit xxxx
|
||||
// - _1 is _
|
||||
// - _2 is ;
|
||||
// - _3 is [
|
||||
// - __ is the separator between function name and overload specification
|
||||
// - _ is otherwise the separator between packages/classes/methods
|
||||
//
|
||||
// This method takes a StringRef & and mutates it and can be called twice on a Jnicall function name to get
|
||||
// first the demangled name
|
||||
// second the demangled overload specification
|
||||
// But we don't use the later for now because we have no way to map that to how SCIP resolves overloads.
|
||||
optional<std::string> demangleJnicallPart(StringRef &remainder)
|
||||
{
|
||||
std::string demangled;
|
||||
|
||||
std::mbstate_t ps = {};
|
||||
|
||||
while (!remainder.empty()) {
|
||||
switch (remainder[0]) {
|
||||
case '0': {
|
||||
remainder = remainder.drop_front(1);
|
||||
|
||||
uint16_t codeUnit;
|
||||
const auto ok = remainder.substr(1, 4).getAsInteger(16, codeUnit);
|
||||
remainder = remainder.drop_front(4);
|
||||
|
||||
if (!ok) // failed reading xxxx as hexadecimal from _0xxxx
|
||||
return {};
|
||||
|
||||
std::array<char, MB_LEN_MAX> codePoint;
|
||||
const auto mbLen = std::c16rtomb(codePoint.data(), codeUnit, &ps);
|
||||
|
||||
if (mbLen == -1) // failed converting utf16 to utf8
|
||||
return {};
|
||||
|
||||
demangled += StringRef(codePoint.begin(), mbLen);
|
||||
break;
|
||||
}
|
||||
case '1':
|
||||
remainder = remainder.drop_front(1);
|
||||
ps = {};
|
||||
demangled += '_';
|
||||
break;
|
||||
case '2':
|
||||
remainder = remainder.drop_front(1);
|
||||
ps = {};
|
||||
demangled += ';';
|
||||
break;
|
||||
case '3':
|
||||
remainder = remainder.drop_front(1);
|
||||
ps = {};
|
||||
demangled += '[';
|
||||
break;
|
||||
case '_':
|
||||
remainder = remainder.drop_front(1);
|
||||
ps = {};
|
||||
if (remainder.empty()) // the string ends with _
|
||||
return {};
|
||||
|
||||
switch (remainder[0]) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
demangled += '.';
|
||||
break;
|
||||
default:
|
||||
// either:
|
||||
// * the string began with _[^0-3], which is not supposed to happen; or
|
||||
// * we reached __[^0-3] meaning we finished the first part of the name and remainder holds the overload specification
|
||||
return demangled;
|
||||
}
|
||||
default:
|
||||
ps = {};
|
||||
demangled += '.';
|
||||
break;
|
||||
}
|
||||
StringRef token;
|
||||
std::tie(token, remainder) = remainder.split('_');
|
||||
demangled += token;
|
||||
}
|
||||
|
||||
return demangled;
|
||||
}
|
||||
|
||||
optional<std::string> scipSymbolFromJnicallFunctionName(StringRef functionName)
|
||||
{
|
||||
if (!functionName.consume_front("Java_"))
|
||||
return {};
|
||||
|
||||
const auto demangledName = demangleJnicallPart(functionName);
|
||||
|
||||
if (!demangledName || demangledName->empty())
|
||||
return {};
|
||||
|
||||
// demangleJavaName returns something like .some.package.Class$InnerClass.method
|
||||
// - prepend S_jvm_
|
||||
// - remove the leading dot
|
||||
// - replace the last dot with a #
|
||||
// - replace the other dots with /
|
||||
// - replace $ with #
|
||||
// - add the ([+overloadNumber]). suffix
|
||||
auto symbol = JVM_SCIP_SYMBOL_PREFIX.str();
|
||||
symbol += demangledName->substr(1);
|
||||
const auto lastDot = symbol.rfind('.');
|
||||
if (lastDot != std::string::npos)
|
||||
symbol[lastDot] = '#';
|
||||
std::replace(symbol.begin(), symbol.end(), '.', '/');
|
||||
std::replace(symbol.begin(), symbol.end(), '$', '#');
|
||||
|
||||
// Keep track of how many times we have seen this method, to build the ([+overloadNumber]). suffix.
|
||||
// This assumes this function is called on C function definitions in the same order the matching overloads are declared in Java.
|
||||
static std::unordered_map<std::string, uint> jnicallFunctions;
|
||||
auto &overloadNumber = jnicallFunctions[symbol];
|
||||
|
||||
symbol += '(';
|
||||
if (overloadNumber) {
|
||||
symbol += '+';
|
||||
symbol += overloadNumber;
|
||||
overloadNumber++;
|
||||
}
|
||||
symbol += ").";
|
||||
|
||||
return symbol;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// class [wrapper] : public mozilla::jni::ObjectBase<[wrapper]>
|
||||
// {
|
||||
// static constexpr char name[] = "[nameFieldValue]";
|
||||
// }
|
||||
void findBindingToJavaClass(ASTContext &C, CXXRecordDecl &klass)
|
||||
{
|
||||
for (const auto &baseSpecifier : klass.bases()) {
|
||||
const auto *base = baseSpecifier.getType()->getAsCXXRecordDecl();
|
||||
if (!base)
|
||||
continue;
|
||||
|
||||
if (!isMozillaJniObjectBase(*base))
|
||||
continue;
|
||||
|
||||
const auto name = nameFieldValue(klass);
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
const auto symbol = javaScipSymbol(JVM_SCIP_SYMBOL_PREFIX, *name, BindingTo::Kind::Class);
|
||||
const auto binding = BindingTo {{
|
||||
.lang = BindingTo::Lang::Jvm,
|
||||
.kind = BindingTo::Kind::Class,
|
||||
.symbol = symbol,
|
||||
}};
|
||||
|
||||
setBindingAttr(C, klass, binding);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// When a Java method is marked as native, the JRE looks by default for a function
|
||||
// named Java_<mangled method name>[__<mangled overload specification>].
|
||||
void findBindingToJavaFunction(ASTContext &C, FunctionDecl &function)
|
||||
{
|
||||
const auto *identifier = function.getIdentifier();
|
||||
if (!identifier)
|
||||
return;
|
||||
|
||||
const auto name = identifier->getName();
|
||||
const auto symbol = scipSymbolFromJnicallFunctionName(name);
|
||||
if (!symbol)
|
||||
return;
|
||||
|
||||
const auto binding = BoundAs {{
|
||||
.lang = BindingTo::Lang::Jvm,
|
||||
.kind = BindingTo::Kind::Method,
|
||||
.symbol = *symbol,
|
||||
}};
|
||||
|
||||
setBindingAttr(C, function, binding);
|
||||
}
|
||||
|
||||
// class [parent]
|
||||
// {
|
||||
// struct [methodStruct] {
|
||||
// static constexpr char name[] = "[methodNameFieldValue]";
|
||||
// }
|
||||
// [method]
|
||||
// {
|
||||
// ...
|
||||
// mozilla::jni::{Method,Constructor,Field}<[methodStruct]>::{Call,Get,Set}(...)
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
void findBindingToJavaMember(ASTContext &C, CXXMethodDecl &method)
|
||||
{
|
||||
const auto *parent = method.getParent();
|
||||
if (!parent)
|
||||
return;
|
||||
const auto classBinding = getBindingTo(*parent);
|
||||
if (!classBinding)
|
||||
return;
|
||||
|
||||
auto *body = method.getBody();
|
||||
if (!body)
|
||||
return;
|
||||
|
||||
const auto found = FindCallCall::search(body);
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
const auto symbol = javaScipSymbol(classBinding->symbol, found->name, found->kind);
|
||||
const auto binding = BindingTo {{
|
||||
.lang = BindingTo::Lang::Jvm,
|
||||
.kind = found->kind,
|
||||
.symbol = symbol,
|
||||
}};
|
||||
|
||||
setBindingAttr(C, method, binding);
|
||||
}
|
||||
|
||||
// class [parent]
|
||||
// {
|
||||
// struct [methodStruct] {
|
||||
// static constexpr char name[] = "[methodNameFieldValue]";
|
||||
// }
|
||||
// [method]
|
||||
// {
|
||||
// ...
|
||||
// mozilla::jni::{Method,Constructor,Field}<[methodStruct]>::{Call,Get,Set}(...)
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
void findBindingToJavaConstant(ASTContext &C, VarDecl &field)
|
||||
{
|
||||
const auto *parent = dyn_cast_or_null<CXXRecordDecl>(field.getDeclContext());
|
||||
if (!parent)
|
||||
return;
|
||||
|
||||
const auto classBinding = getBindingTo(*parent);
|
||||
if (!classBinding)
|
||||
return;
|
||||
|
||||
const auto symbol = javaScipSymbol(classBinding->symbol, field.getName(), BindingTo::Kind::Const);
|
||||
const auto binding = BindingTo {{
|
||||
.lang = BindingTo::Lang::Jvm,
|
||||
.kind = BindingTo::Kind::Const,
|
||||
.symbol = symbol,
|
||||
}};
|
||||
|
||||
setBindingAttr(C, field, binding);
|
||||
}
|
||||
|
||||
// class [klass] : public [wrapper]::Natives<[klass]> {...}
|
||||
// class [wrapper] : public mozilla::jni::ObjectBase<[wrapper]>
|
||||
// {
|
||||
// static constexpr char name[] = "[nameFieldValue]";
|
||||
//
|
||||
// struct [methodStruct] {
|
||||
// static constexpr char name[] = "[methodNameFieldValue]";
|
||||
// }
|
||||
//
|
||||
// template<typename T>
|
||||
// class [wrapper]::Natives : public mozilla::jni::NativeImpl<[wrapper], T> {
|
||||
// static const JNINativeMethod methods[] = {
|
||||
// mozilla::jni::MakeNativeMethod<[wrapper]::[methodStruct]>(
|
||||
// mozilla::jni::NativeStub<[wrapper]::[methodStruct], Impl>
|
||||
// ::template Wrap<&Impl::[method]>),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
void findBoundAsJavaClasses(ASTContext &C, CXXRecordDecl &klass)
|
||||
{
|
||||
for (const auto &baseSpecifier : klass.bases()) {
|
||||
const auto *base = baseSpecifier.getType()->getAsCXXRecordDecl();
|
||||
if (!base)
|
||||
continue;
|
||||
|
||||
for (const auto &baseBaseSpecifier : base->bases()) {
|
||||
const auto *baseBase = dyn_cast_or_null<ClassTemplateSpecializationDecl>(baseBaseSpecifier.getType()->getAsCXXRecordDecl());
|
||||
if (!baseBase)
|
||||
continue;
|
||||
|
||||
if (!isMozillaJniNativeImpl(*baseBase))
|
||||
continue;
|
||||
|
||||
const auto *wrapper = baseBase->getTemplateArgs().get(0).getAsType()->getAsCXXRecordDecl();
|
||||
|
||||
if (!wrapper)
|
||||
continue;
|
||||
|
||||
const auto name = nameFieldValue(*wrapper);
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
const auto javaClassSymbol = javaScipSymbol(JVM_SCIP_SYMBOL_PREFIX, *name, BoundAs::Kind::Class);
|
||||
const auto classBinding = BoundAs {{
|
||||
.lang = BoundAs::Lang::Jvm,
|
||||
.kind = BoundAs::Kind::Class,
|
||||
.symbol = javaClassSymbol,
|
||||
}};
|
||||
setBindingAttr(C, klass, classBinding);
|
||||
|
||||
const auto *methodsDecl = dyn_cast_or_null<VarDecl>(fieldNamed("methods", *base));
|
||||
if (!methodsDecl)
|
||||
continue;
|
||||
|
||||
const auto *methodsDef = methodsDecl->getDefinition();
|
||||
if (!methodsDef)
|
||||
continue;
|
||||
|
||||
const auto *inits = dyn_cast_or_null<InitListExpr>(methodsDef->getInit());
|
||||
if (!inits)
|
||||
continue;
|
||||
|
||||
std::set<const CXXMethodDecl *> alreadyBound;
|
||||
|
||||
for (const auto *init : inits->inits()) {
|
||||
const auto *call = dyn_cast<CallExpr>(init->IgnoreUnlessSpelledInSource());
|
||||
if (!call)
|
||||
continue;
|
||||
|
||||
const auto *funcDecl = call->getDirectCallee();
|
||||
if (!funcDecl)
|
||||
continue;
|
||||
|
||||
const auto *templateArgs = funcDecl->getTemplateSpecializationArgs();
|
||||
if (!templateArgs)
|
||||
continue;
|
||||
|
||||
const auto *strukt = dyn_cast_or_null<RecordDecl>(templateArgs->get(0).getAsType()->getAsRecordDecl());
|
||||
if (!strukt)
|
||||
continue;
|
||||
|
||||
const auto *wrapperRef = dyn_cast_or_null<DeclRefExpr>(call->getArg(0)->IgnoreUnlessSpelledInSource());
|
||||
if (!wrapperRef)
|
||||
continue;
|
||||
|
||||
const auto *boundRef = dyn_cast_or_null<UnaryOperator>(wrapperRef->template_arguments().front().getArgument().getAsExpr());
|
||||
if (!boundRef)
|
||||
continue;
|
||||
|
||||
auto addToBound = [&](CXXMethodDecl &boundDecl, uint overloadNum) {
|
||||
const auto methodName = nameFieldValue(*strukt);
|
||||
if (!methodName)
|
||||
return;
|
||||
|
||||
auto javaMethodSymbol = javaClassSymbol;
|
||||
javaMethodSymbol += *methodName;
|
||||
javaMethodSymbol += '(';
|
||||
if (overloadNum > 0) {
|
||||
javaMethodSymbol += '+';
|
||||
javaMethodSymbol += std::to_string(overloadNum);
|
||||
}
|
||||
javaMethodSymbol += ").";
|
||||
|
||||
const auto binding = BoundAs {{
|
||||
.lang = BoundAs::Lang::Jvm,
|
||||
.kind = BoundAs::Kind::Method,
|
||||
.symbol = javaMethodSymbol,
|
||||
}};
|
||||
setBindingAttr(C, boundDecl, binding);
|
||||
};
|
||||
|
||||
if (auto *bound = dyn_cast_or_null<DeclRefExpr>(boundRef->getSubExpr())) {
|
||||
auto *method = dyn_cast_or_null<CXXMethodDecl>(bound->getDecl());
|
||||
if (!method)
|
||||
continue;
|
||||
addToBound(*method, 0);
|
||||
} else if (const auto *bound = dyn_cast_or_null<UnresolvedLookupExpr>(boundRef->getSubExpr())) {
|
||||
// XXX This is hackish
|
||||
// In case of overloads it's not obvious which one we should use
|
||||
// this expects the declaration order between C++ and Java to match
|
||||
auto declarations = std::vector<Decl*>(bound->decls_begin(), bound->decls_end());
|
||||
auto byLocation = [](Decl *a, Decl *b){ return a->getLocation() < b->getLocation(); };
|
||||
std::sort(declarations.begin(), declarations.end(), byLocation);
|
||||
|
||||
uint i = 0;
|
||||
for (auto *decl : declarations) {
|
||||
auto *method = dyn_cast<CXXMethodDecl>(decl);
|
||||
if (!method)
|
||||
continue;
|
||||
if (alreadyBound.find(method) == alreadyBound.end()) {
|
||||
addToBound(*method, i);
|
||||
alreadyBound.insert(method);
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void emitBindingAttributes(llvm::json::OStream &J, const Decl &decl)
|
||||
{
|
||||
addSlotOwnerAttribute(J, decl);
|
||||
addBindingSlotsAttribute(J, decl);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <clang/AST/ASTContext.h>
|
||||
#include <clang/AST/Decl.h>
|
||||
#include <clang/AST/DeclCXX.h>
|
||||
#include <llvm/Support/JSON.h>
|
||||
|
||||
void findBindingToJavaClass(clang::ASTContext &C, clang::CXXRecordDecl &klass);
|
||||
void findBoundAsJavaClasses(clang::ASTContext &C, clang::CXXRecordDecl &klass);
|
||||
void findBindingToJavaFunction(clang::ASTContext &C, clang::FunctionDecl &function);
|
||||
void findBindingToJavaMember(clang::ASTContext &C, clang::CXXMethodDecl &method);
|
||||
void findBindingToJavaConstant(clang::ASTContext &C, clang::VarDecl &field);
|
||||
|
||||
void emitBindingAttributes(llvm::json::OStream &json, const clang::Decl &decl);
|
|
@ -35,6 +35,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "BindingOperations.h"
|
||||
#include "FileOperations.h"
|
||||
#include "StringOperations.h"
|
||||
#include "from-clangd/HeuristicResolver.h"
|
||||
|
@ -1054,6 +1055,8 @@ public:
|
|||
|
||||
J.attribute("sizeBytes", Layout.getSize().getQuantity());
|
||||
|
||||
emitBindingAttributes(J, *decl);
|
||||
|
||||
auto cxxDecl = dyn_cast<CXXRecordDecl>(decl);
|
||||
|
||||
if (cxxDecl) {
|
||||
|
@ -1197,6 +1200,8 @@ public:
|
|||
J.attribute("pretty", getQualifiedName(decl));
|
||||
J.attribute("sym", getMangledName(CurMangleContext, decl));
|
||||
|
||||
emitBindingAttributes(J, *decl);
|
||||
|
||||
auto cxxDecl = dyn_cast<CXXMethodDecl>(decl);
|
||||
|
||||
if (cxxDecl) {
|
||||
|
@ -1316,6 +1321,42 @@ public:
|
|||
F->Output.push_back(std::move(ros.str()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit structured info for a variable if it is a static class member.
|
||||
*/
|
||||
void emitStructuredInfo(SourceLocation Loc, const VarDecl *decl) {
|
||||
const auto *parentDecl = dyn_cast_or_null<RecordDecl>(decl->getDeclContext());
|
||||
|
||||
std::string json_str;
|
||||
llvm::raw_string_ostream ros(json_str);
|
||||
llvm::json::OStream J(ros);
|
||||
// Start the top-level object.
|
||||
J.objectBegin();
|
||||
|
||||
unsigned StartOffset = SM.getFileOffset(Loc);
|
||||
unsigned EndOffset =
|
||||
StartOffset + Lexer::MeasureTokenLength(Loc, SM, CI.getLangOpts());
|
||||
J.attribute("loc", locationToString(Loc, EndOffset - StartOffset));
|
||||
J.attribute("structured", 1);
|
||||
J.attribute("pretty", getQualifiedName(decl));
|
||||
J.attribute("sym", getMangledName(CurMangleContext, decl));
|
||||
J.attribute("kind", "field");
|
||||
|
||||
if (parentDecl) {
|
||||
J.attribute("parentsym", getMangledName(CurMangleContext, parentDecl));
|
||||
}
|
||||
|
||||
emitBindingAttributes(J, *decl);
|
||||
|
||||
// End the top-level object.
|
||||
J.objectEnd();
|
||||
|
||||
FileInfo *F = getFileInfo(Loc);
|
||||
// we want a newline.
|
||||
ros << '\n';
|
||||
F->Output.push_back(std::move(ros.str()));
|
||||
}
|
||||
|
||||
// XXX Type annotating.
|
||||
// QualType is the type class. It has helpers like TagDecl via getAsTagDecl.
|
||||
// ValueDecl exposes a getType() method.
|
||||
|
@ -1744,6 +1785,10 @@ public:
|
|||
// we can better evaluate what is useful.
|
||||
!D2->isDependentType() &&
|
||||
!TemplateStack) {
|
||||
if (auto *D3 = dyn_cast<CXXRecordDecl>(D2)) {
|
||||
findBindingToJavaClass(*AstContext, *D3);
|
||||
findBoundAsJavaClasses(*AstContext, *D3);
|
||||
}
|
||||
emitStructuredInfo(Loc, D2);
|
||||
}
|
||||
}
|
||||
|
@ -1755,6 +1800,11 @@ public:
|
|||
!wasTemplate &&
|
||||
!D2->isFunctionTemplateSpecialization() &&
|
||||
!TemplateStack) {
|
||||
if (auto *D3 = dyn_cast<CXXMethodDecl>(D2)) {
|
||||
findBindingToJavaMember(*AstContext, *D3);
|
||||
} else {
|
||||
findBindingToJavaFunction(*AstContext, *D2);
|
||||
}
|
||||
emitStructuredInfo(Loc, D2);
|
||||
}
|
||||
}
|
||||
|
@ -1764,6 +1814,14 @@ public:
|
|||
emitStructuredInfo(Loc, D2);
|
||||
}
|
||||
}
|
||||
if (VarDecl *D2 = dyn_cast<VarDecl>(D)) {
|
||||
if (!D2->isTemplated() &&
|
||||
!TemplateStack &&
|
||||
isa<CXXRecordDecl>(D2->getDeclContext())) {
|
||||
findBindingToJavaConstant(*AstContext, *D2);
|
||||
emitStructuredInfo(Loc, D2);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче