mirror of
https://github.com/MaskRay/ccls.git
synced 2024-12-01 11:57:09 +00:00
Fix unaligned load/store; add index.multiVersion prototype, rename index.onParse to index.OnOpen
Don't call getFieldOffset() on RD->isInvalidDecl()
This commit is contained in:
parent
1ab0d492b7
commit
a8bb605d4a
22
src/config.h
22
src/config.h
@ -137,8 +137,8 @@ struct Config {
|
|||||||
// xxx: at most every xxx milliseconds
|
// xxx: at most every xxx milliseconds
|
||||||
int frequencyMs = 0;
|
int frequencyMs = 0;
|
||||||
|
|
||||||
// If true, diagnostics from a full document parse will be reported.
|
// If true, diagnostics will be reported in textDocument/didOpen.
|
||||||
bool onParse = true;
|
bool onOpen = true;
|
||||||
|
|
||||||
// If true, diagnostics from typing will be reported.
|
// If true, diagnostics from typing will be reported.
|
||||||
bool onType = true;
|
bool onType = true;
|
||||||
@ -159,14 +159,6 @@ struct Config {
|
|||||||
} highlight;
|
} highlight;
|
||||||
|
|
||||||
struct Index {
|
struct Index {
|
||||||
// Attempt to convert calls of make* functions to constructors based on
|
|
||||||
// hueristics.
|
|
||||||
//
|
|
||||||
// For example, this will show constructor calls for std::make_unique
|
|
||||||
// invocations. Specifically, ccls will try to attribute a ctor call
|
|
||||||
// whenever the function name starts with make (ignoring case).
|
|
||||||
bool attributeMakeCallsToCtor = true;
|
|
||||||
|
|
||||||
// If a translation unit's absolute path matches any EMCAScript regex in the
|
// If a translation unit's absolute path matches any EMCAScript regex in the
|
||||||
// whitelist, or does not match any regex in the blacklist, it will be
|
// whitelist, or does not match any regex in the blacklist, it will be
|
||||||
// indexed. To only index files in the whitelist, add ".*" to the blacklist.
|
// indexed. To only index files in the whitelist, add ".*" to the blacklist.
|
||||||
@ -184,6 +176,9 @@ struct Config {
|
|||||||
// If false, the indexer will be disabled.
|
// If false, the indexer will be disabled.
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
|
||||||
|
// If not 0, a file will be indexed in each tranlation unit that includes it.
|
||||||
|
int multiVersion = 0;
|
||||||
|
|
||||||
// Allow indexing on textDocument/didChange.
|
// Allow indexing on textDocument/didChange.
|
||||||
// May be too slow for big projects, so it is off by default.
|
// May be too slow for big projects, so it is off by default.
|
||||||
bool onDidChange = false;
|
bool onDidChange = false;
|
||||||
@ -226,12 +221,11 @@ MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, dropOldRequests,
|
|||||||
detailedLabel, filterAndSort, includeBlacklist,
|
detailedLabel, filterAndSort, includeBlacklist,
|
||||||
includeMaxPathSize, includeSuffixWhitelist,
|
includeMaxPathSize, includeSuffixWhitelist,
|
||||||
includeWhitelist);
|
includeWhitelist);
|
||||||
MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, frequencyMs, onParse,
|
MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, frequencyMs, onOpen,
|
||||||
onType, whitelist)
|
onType, whitelist)
|
||||||
MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist)
|
MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist)
|
||||||
MAKE_REFLECT_STRUCT(Config::Index, attributeMakeCallsToCtor, blacklist,
|
MAKE_REFLECT_STRUCT(Config::Index, blacklist, comments, enabled, multiVersion,
|
||||||
comments, enabled, onDidChange, reparseForDependency,
|
onDidChange, reparseForDependency, threads, whitelist);
|
||||||
threads, whitelist);
|
|
||||||
MAKE_REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort);
|
MAKE_REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort);
|
||||||
MAKE_REFLECT_STRUCT(Config::Xref, container, maxNum);
|
MAKE_REFLECT_STRUCT(Config::Xref, container, maxNum);
|
||||||
MAKE_REFLECT_STRUCT(Config, compilationDatabaseCommand,
|
MAKE_REFLECT_STRUCT(Config, compilationDatabaseCommand,
|
||||||
|
@ -48,9 +48,7 @@ struct IndexParam {
|
|||||||
|
|
||||||
IndexParam(FileConsumer *file_consumer) : file_consumer(file_consumer) {}
|
IndexParam(FileConsumer *file_consumer) : file_consumer(file_consumer) {}
|
||||||
|
|
||||||
IndexFile *ConsumeFile(const FileEntry &File) {
|
void SeenFile(const FileEntry &File) {
|
||||||
IndexFile *db = file_consumer->TryConsumeFile(File, &file_contents);
|
|
||||||
|
|
||||||
// If this is the first time we have seen the file (ignoring if we are
|
// If this is the first time we have seen the file (ignoring if we are
|
||||||
// generating an index for it):
|
// generating an index for it):
|
||||||
auto [it, inserted] = SeenUniqueID.try_emplace(File.getUniqueID());
|
auto [it, inserted] = SeenUniqueID.try_emplace(File.getUniqueID());
|
||||||
@ -65,8 +63,11 @@ struct IndexParam {
|
|||||||
if (write_time)
|
if (write_time)
|
||||||
file2write_time[file_name] = *write_time;
|
file2write_time[file_name] = *write_time;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return db;
|
IndexFile *ConsumeFile(const FileEntry &File) {
|
||||||
|
SeenFile(File);
|
||||||
|
return file_consumer->TryConsumeFile(File, &file_contents);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -392,20 +393,20 @@ public:
|
|||||||
return it->second.usr;
|
return it->second.usr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Use GetUse(IndexFile *db, Range range, const DeclContext *DC,
|
Use GetUse(IndexFile *db, int lid, Range range, const DeclContext *DC,
|
||||||
Role role) const {
|
Role role) const {
|
||||||
if (!DC)
|
if (!DC)
|
||||||
return Use{{range, 0, SymbolKind::File, role}};
|
return {{range, 0, SymbolKind::File, role}, lid};
|
||||||
const Decl *D = cast<Decl>(DC);
|
const Decl *D = cast<Decl>(DC);
|
||||||
switch (GetSymbolKind(D)) {
|
switch (GetSymbolKind(D)) {
|
||||||
case SymbolKind::Func:
|
case SymbolKind::Func:
|
||||||
return Use{{range, db->ToFunc(GetUsr(D)).usr, SymbolKind::Func, role}};
|
return {{range, db->ToFunc(GetUsr(D)).usr, SymbolKind::Func, role}, lid};
|
||||||
case SymbolKind::Type:
|
case SymbolKind::Type:
|
||||||
return Use{{range, db->ToType(GetUsr(D)).usr, SymbolKind::Type, role}};
|
return {{range, db->ToType(GetUsr(D)).usr, SymbolKind::Type, role}, lid};
|
||||||
case SymbolKind::Var:
|
case SymbolKind::Var:
|
||||||
return Use{{range, db->ToVar(GetUsr(D)).usr, SymbolKind::Var, role}};
|
return {{range, db->ToVar(GetUsr(D)).usr, SymbolKind::Var, role}, lid};
|
||||||
default:
|
default:
|
||||||
return Use{{range, 0, SymbolKind::File, role}};
|
return {{range, 0, SymbolKind::File, role}, lid};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,26 +536,32 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int GetFileLID(IndexFile *db, SourceManager &SM, const FileEntry &FE) {
|
||||||
|
auto [it, inserted] = db->uid2lid_and_path.try_emplace(FE.getUniqueID());
|
||||||
|
if (inserted) {
|
||||||
|
it->second.first = db->uid2lid_and_path.size() - 1;
|
||||||
|
SmallString<256> Path = FE.tryGetRealPathName();
|
||||||
|
if (Path.empty())
|
||||||
|
Path = FE.getName();
|
||||||
|
if (!llvm::sys::path::is_absolute(Path) &&
|
||||||
|
!SM.getFileManager().makeAbsolutePath(Path))
|
||||||
|
return -1;
|
||||||
|
it->second.second = Path.str();
|
||||||
|
}
|
||||||
|
return it->second.first;
|
||||||
|
}
|
||||||
|
|
||||||
void AddMacroUse(IndexFile *db, SourceManager &SM, Usr usr, SymbolKind kind,
|
void AddMacroUse(IndexFile *db, SourceManager &SM, Usr usr, SymbolKind kind,
|
||||||
SourceLocation Spell) const {
|
SourceLocation Spell) const {
|
||||||
const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Spell));
|
const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Spell));
|
||||||
if (!FE)
|
if (!FE)
|
||||||
return;
|
return;
|
||||||
auto UID = FE->getUniqueID();
|
int lid = GetFileLID(db, SM, *FE);
|
||||||
auto [it, inserted] = db->uid2lid_and_path.try_emplace(UID);
|
if (lid < 0)
|
||||||
if (inserted) {
|
|
||||||
it->second.first = db->uid2lid_and_path.size() - 1;
|
|
||||||
SmallString<256> Path = FE->tryGetRealPathName();
|
|
||||||
if (Path.empty())
|
|
||||||
Path = FE->getName();
|
|
||||||
if (!llvm::sys::path::is_absolute(Path) &&
|
|
||||||
!SM.getFileManager().makeAbsolutePath(Path))
|
|
||||||
return;
|
return;
|
||||||
it->second.second = Path.str();
|
|
||||||
}
|
|
||||||
Range spell =
|
Range spell =
|
||||||
FromTokenRange(SM, Ctx->getLangOpts(), SourceRange(Spell, Spell));
|
FromTokenRange(SM, Ctx->getLangOpts(), SourceRange(Spell, Spell));
|
||||||
Use use{{spell, 0, SymbolKind::File, Role::Dynamic}, it->second.first};
|
Use use{{spell, 0, SymbolKind::File, Role::Dynamic}, lid};
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case SymbolKind::Func:
|
case SymbolKind::Func:
|
||||||
db->ToFunc(usr).uses.push_back(use);
|
db->ToFunc(usr).uses.push_back(use);
|
||||||
@ -615,15 +622,26 @@ public:
|
|||||||
FE = SM.getFileEntryForID(LocFID);
|
FE = SM.getFileEntryForID(LocFID);
|
||||||
if (!FE)
|
if (!FE)
|
||||||
return true;
|
return true;
|
||||||
IndexFile *db = param.ConsumeFile(*FE);
|
int lid = -1;
|
||||||
|
IndexFile *db;
|
||||||
|
if (g_config->index.multiVersion) {
|
||||||
|
db = param.ConsumeFile(*SM.getFileEntryForID(SM.getMainFileID()));
|
||||||
if (!db)
|
if (!db)
|
||||||
return true;
|
return true;
|
||||||
|
param.SeenFile(*FE);
|
||||||
|
if (!SM.isInMainFile(R.getBegin()))
|
||||||
|
lid = GetFileLID(db, SM, *FE);
|
||||||
|
} else {
|
||||||
|
db = param.ConsumeFile(*FE);
|
||||||
|
if (!db)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const Decl *OrigD = ASTNode.OrigD;
|
const Decl *OrigD = ASTNode.OrigD;
|
||||||
const DeclContext *SemDC = OrigD->getDeclContext();
|
const DeclContext *SemDC = OrigD->getDeclContext();
|
||||||
const DeclContext *LexDC = ASTNode.ContainerDC;
|
const DeclContext *LexDC = ASTNode.ContainerDC;
|
||||||
Role role = static_cast<Role>(Roles);
|
Role role = static_cast<Role>(Roles);
|
||||||
db->language = std::max(db->language, GetDeclLanguage(OrigD));
|
db->language = LanguageId((int)db->language | (int)GetDeclLanguage(OrigD));
|
||||||
|
|
||||||
bool is_decl = Roles & uint32_t(index::SymbolRole::Declaration);
|
bool is_decl = Roles & uint32_t(index::SymbolRole::Declaration);
|
||||||
bool is_def = Roles & uint32_t(index::SymbolRole::Definition);
|
bool is_def = Roles & uint32_t(index::SymbolRole::Definition);
|
||||||
@ -638,18 +656,18 @@ public:
|
|||||||
|
|
||||||
auto do_def_decl = [&](auto *entity) {
|
auto do_def_decl = [&](auto *entity) {
|
||||||
if (is_def) {
|
if (is_def) {
|
||||||
entity->def.spell = GetUse(db, loc, SemDC, role);
|
entity->def.spell = GetUse(db, lid, loc, SemDC, role);
|
||||||
SourceRange R = OrigD->getSourceRange();
|
SourceRange R = OrigD->getSourceRange();
|
||||||
entity->def.extent =
|
entity->def.extent =
|
||||||
GetUse(db,
|
GetUse(db, lid,
|
||||||
R.getBegin().isFileID()
|
R.getBegin().isFileID()
|
||||||
? FromTokenRange(SM, Lang, OrigD->getSourceRange())
|
? FromTokenRange(SM, Lang, OrigD->getSourceRange())
|
||||||
: loc,
|
: loc,
|
||||||
LexDC, Role::None);
|
LexDC, Role::None);
|
||||||
} else if (is_decl) {
|
} else if (is_decl) {
|
||||||
entity->declarations.push_back(GetUse(db, loc, LexDC, role));
|
entity->declarations.push_back(GetUse(db, lid, loc, LexDC, role));
|
||||||
} else {
|
} else {
|
||||||
entity->uses.push_back(GetUse(db, loc, LexDC, role));
|
entity->uses.push_back(GetUse(db, lid, loc, LexDC, role));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (entity->def.comments[0] == '\0' && g_config->index.comments)
|
if (entity->def.comments[0] == '\0' && g_config->index.comments)
|
||||||
@ -735,10 +753,11 @@ public:
|
|||||||
if (SM.getFileID(R1.getBegin()) == LocFID) {
|
if (SM.getFileID(R1.getBegin()) == LocFID) {
|
||||||
IndexType &type1 = db->ToType(usr1);
|
IndexType &type1 = db->ToType(usr1);
|
||||||
SourceLocation L1 = D1->getLocation();
|
SourceLocation L1 = D1->getLocation();
|
||||||
type1.def.spell = GetUse(db, FromTokenRange(SM, Lang, {L1, L1}),
|
type1.def.spell =
|
||||||
SemDC, Role::Definition);
|
GetUse(db, lid, FromTokenRange(SM, Lang, {L1, L1}), SemDC,
|
||||||
type1.def.extent =
|
Role::Definition);
|
||||||
GetUse(db, FromTokenRange(SM, Lang, R1), LexDC, Role::None);
|
type1.def.extent = GetUse(db, lid, FromTokenRange(SM, Lang, R1),
|
||||||
|
LexDC, Role::None);
|
||||||
type1.def.detailed_name = Intern(info1->short_name);
|
type1.def.detailed_name = Intern(info1->short_name);
|
||||||
type1.def.short_name_size = int16_t(info1->short_name.size());
|
type1.def.short_name_size = int16_t(info1->short_name.size());
|
||||||
type1.def.kind = lsSymbolKind::TypeParameter;
|
type1.def.kind = lsSymbolKind::TypeParameter;
|
||||||
@ -753,10 +772,10 @@ public:
|
|||||||
// e.g. lambda parameter
|
// e.g. lambda parameter
|
||||||
SourceLocation L = OrigD->getLocation();
|
SourceLocation L = OrigD->getLocation();
|
||||||
if (SM.getFileID(L) == LocFID) {
|
if (SM.getFileID(L) == LocFID) {
|
||||||
var->def.spell = GetUse(db, FromTokenRange(SM, Lang, {L, L}), SemDC,
|
var->def.spell = GetUse(db, lid, FromTokenRange(SM, Lang, {L, L}),
|
||||||
Role::Definition);
|
SemDC, Role::Definition);
|
||||||
var->def.extent =
|
var->def.extent =
|
||||||
GetUse(db, FromTokenRange(SM, Lang, OrigD->getSourceRange()),
|
GetUse(db, lid, FromTokenRange(SM, Lang, OrigD->getSourceRange()),
|
||||||
LexDC, Role::None);
|
LexDC, Role::None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -853,10 +872,10 @@ public:
|
|||||||
std::tie(RD, offset) = Stack.back();
|
std::tie(RD, offset) = Stack.back();
|
||||||
Stack.pop_back();
|
Stack.pop_back();
|
||||||
if (!RD->isCompleteDefinition() || RD->isDependentType() ||
|
if (!RD->isCompleteDefinition() || RD->isDependentType() ||
|
||||||
!ValidateRecord(RD))
|
RD->isInvalidDecl() || !ValidateRecord(RD))
|
||||||
offset = -1;
|
offset = -1;
|
||||||
for (FieldDecl *FD : RD->fields()) {
|
for (FieldDecl *FD : RD->fields()) {
|
||||||
int offset1 = offset >= 0 ? offset + Ctx->getFieldOffset(FD) : -1;
|
int offset1 = offset < 0 ? -1 : offset + Ctx->getFieldOffset(FD);
|
||||||
if (FD->getIdentifier())
|
if (FD->getIdentifier())
|
||||||
type->def.vars.emplace_back(GetUsr(FD), offset1);
|
type->def.vars.emplace_back(GetUsr(FD), offset1);
|
||||||
else if (const auto *RT1 = FD->getType()->getAs<RecordType>()) {
|
else if (const auto *RT1 = FD->getType()->getAs<RecordType>()) {
|
||||||
@ -913,7 +932,8 @@ public:
|
|||||||
SourceLocation L1 = TSI->getTypeLoc().getBeginLoc();
|
SourceLocation L1 = TSI->getTypeLoc().getBeginLoc();
|
||||||
if (SM.getFileID(L1) == LocFID) {
|
if (SM.getFileID(L1) == LocFID) {
|
||||||
Range loc1 = FromTokenRange(SM, Lang, {L1, L1});
|
Range loc1 = FromTokenRange(SM, Lang, {L1, L1});
|
||||||
type1.uses.push_back(GetUse(db, loc1, LexDC, Role::Reference));
|
type1.uses.push_back(
|
||||||
|
GetUse(db, lid, loc1, LexDC, Role::Reference));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1066,8 +1086,7 @@ public:
|
|||||||
if (!FE)
|
if (!FE)
|
||||||
return;
|
return;
|
||||||
if (IndexFile *db = param.ConsumeFile(*FE)) {
|
if (IndexFile *db = param.ConsumeFile(*FE)) {
|
||||||
auto [Name, usr] = GetMacro(Tok);
|
IndexVar &var = db->ToVar(GetMacro(Tok).second);
|
||||||
IndexVar &var = db->ToVar(usr);
|
|
||||||
var.uses.push_back(
|
var.uses.push_back(
|
||||||
{{FromTokenRange(SM, param.Ctx->getLangOpts(), {L, L}, &UniqueID), 0,
|
{{FromTokenRange(SM, param.Ctx->getLangOpts(), {L, L}, &UniqueID), 0,
|
||||||
SymbolKind::File, Role::Dynamic}});
|
SymbolKind::File, Role::Dynamic}});
|
||||||
|
@ -229,7 +229,7 @@ struct IndexFile {
|
|||||||
std::string path;
|
std::string path;
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
int64_t last_write_time = 0;
|
int64_t last_write_time = 0;
|
||||||
LanguageId language = LanguageId::Unknown;
|
LanguageId language = LanguageId::C;
|
||||||
|
|
||||||
// uid2lid_and_path is used to generate lid2path, but not serialized.
|
// uid2lid_and_path is used to generate lid2path, but not serialized.
|
||||||
std::unordered_map<llvm::sys::fs::UniqueID, std::pair<int, std::string>>
|
std::unordered_map<llvm::sys::fs::UniqueID, std::pair<int, std::string>>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
// Used to identify the language at a file level. The ordering is important, as
|
// Used to identify the language at a file level. The ordering is important, as
|
||||||
// a file previously identified as `C`, will be changed to `Cpp` if it
|
// a file previously identified as `C`, will be changed to `Cpp` if it
|
||||||
// encounters a c++ declaration.
|
// encounters a c++ declaration.
|
||||||
enum class LanguageId { Unknown = 0, C = 1, Cpp = 2, ObjC = 3, ObjCpp = 4 };
|
enum class LanguageId { Unknown = -1, C = 0, Cpp = 1, ObjC = 2, ObjCpp = 3 };
|
||||||
MAKE_REFLECT_TYPE_PROXY(LanguageId);
|
MAKE_REFLECT_TYPE_PROXY(LanguageId);
|
||||||
|
|
||||||
LanguageId SourceFileLanguage(std::string_view path);
|
LanguageId SourceFileLanguage(std::string_view path);
|
||||||
|
@ -66,7 +66,7 @@ struct Handler_TextDocumentDidOpen
|
|||||||
}
|
}
|
||||||
|
|
||||||
clang_complete->NotifyView(path);
|
clang_complete->NotifyView(path);
|
||||||
if (g_config->diagnostics.onParse)
|
if (g_config->diagnostics.onOpen)
|
||||||
clang_complete->DiagnosticsUpdate({params.textDocument.uri});
|
clang_complete->DiagnosticsUpdate({params.textDocument.uri});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void AssignFileId(const Lid2file_id &, int file_id, SymbolRef &ref) {
|
void AssignFileId(const Lid2file_id &lid2file_id, int file_id, SymbolRef &ref) {
|
||||||
if (ref.kind == SymbolKind::File)
|
if (ref.kind == SymbolKind::File)
|
||||||
ref.usr = file_id;
|
ref.usr = file_id;
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
|
|
||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <string.h>
|
||||||
|
|
||||||
class BinaryReader : public Reader {
|
class BinaryReader : public Reader {
|
||||||
const char *p_;
|
const char *p_;
|
||||||
|
|
||||||
template <typename T> T Get() {
|
template <typename T> T Get() {
|
||||||
auto ret = *reinterpret_cast<const T *>(p_);
|
T ret;
|
||||||
|
memcpy(&ret, p_, sizeof(T));
|
||||||
p_ += sizeof(T);
|
p_ += sizeof(T);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@ -77,7 +78,7 @@ class BinaryWriter : public Writer {
|
|||||||
template <typename T> void Pack(T x) {
|
template <typename T> void Pack(T x) {
|
||||||
auto i = buf_.size();
|
auto i = buf_.size();
|
||||||
buf_.resize(i + sizeof(x));
|
buf_.resize(i + sizeof(x));
|
||||||
*reinterpret_cast<T *>(buf_.data() + i) = x;
|
memcpy(buf_.data() + i, &x, sizeof(x));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VarUInt(uint64_t n) {
|
void VarUInt(uint64_t n) {
|
||||||
|
Loading…
Reference in New Issue
Block a user