Skip to content

ABIChecker: teach ABI descriptor JSON to also keep track of constant values known at compile-time #41367

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion include/swift/APIDigester/ModuleAnalyzerNodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace api {
const uint8_t DIGESTER_JSON_VERSION = 7; // push SDKNodeRoot to lower-level
const uint8_t DIGESTER_JSON_DEFAULT_VERSION = 0; // Use this version number for files before we have a version number in json.
const StringRef ABIRootKey = "ABIRoot";
const StringRef ConstValuesKey = "ConstValues";

class SDKNode;
typedef SDKNode* NodePtr;
Expand Down Expand Up @@ -737,6 +738,8 @@ struct TypeInitInfo {
StringRef ValueOwnership;
};

struct PayLoad;

class SwiftDeclCollector: public VisibleDeclConsumer {
SDKContext &Ctx;
SDKNode *RootNode;
Expand All @@ -757,7 +760,7 @@ class SwiftDeclCollector: public VisibleDeclConsumer {

// Serialize the content of all roots to a given file using JSON format.
void serialize(StringRef Filename);
static void serialize(StringRef Filename, SDKNode *Root);
static void serialize(StringRef Filename, SDKNode *Root, PayLoad otherInfo);

// After collecting decls, either from imported modules or from a previously
// serialized JSON file, using this function to get the root of the SDK.
Expand Down Expand Up @@ -806,6 +809,7 @@ SDKNodeRoot *getSDKNodeRoot(SDKContext &SDKCtx,

SDKNodeRoot *getEmptySDKNodeRoot(SDKContext &SDKCtx);

void dumpSDKRoot(SDKNodeRoot *Root, PayLoad load, StringRef OutputFile);
void dumpSDKRoot(SDKNodeRoot *Root, StringRef OutputFile);

int dumpSDKContent(const CompilerInvocation &InitInvok,
Expand Down
99 changes: 94 additions & 5 deletions lib/APIDigester/ModuleAnalyzerNodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,9 @@ SDKNode* SDKNode::constructSDKNode(SDKContext &Ctx,
} else if (keyString == ABIRootKey) {
Result = constructSDKNode(Ctx,
cast<llvm::yaml::MappingNode>(Pair.getValue()));
} else if (keyString == ConstValuesKey) {
// We don't need to consume the const values from the compiler-side
Pair.skip();
} else {
Ctx.diagnose(Pair.getKey(), diag::sdk_node_unrecognized_key,
keyString);
Expand Down Expand Up @@ -2215,8 +2218,81 @@ static parseJsonEmit(SDKContext &Ctx, StringRef FileName) {
}
return {std::move(FileBufOrErr.get()), Result};
}
enum class ConstKind: uint8_t {
String = 0,
Int,
};

struct ConstExprInfo {
StringRef filePath;
ConstKind kind;
unsigned offset = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, offset here is measured in number of characters from the start of the buffer, and length is the difference between token locs of the Expr where we found this value.

I'm not sure these are useful values to have here, what's the thinking behind including them?

unsigned length = 0;
StringRef value;
ConstExprInfo(StringRef filePath, ConstKind kind, unsigned offset,
unsigned length, StringRef value):
filePath(filePath), kind(kind), offset(offset), length(length), value(value) {}
ConstExprInfo() = default;
};

class ConstExtractor: public ASTWalker {
SDKContext &SCtx;
ASTContext &Ctx;
SourceManager &SM;
std::vector<ConstExprInfo> allConsts;

void record(Expr *E, ConstKind kind, StringRef Value) {
auto startLoc = E->getStartLoc();
// Asserts?
if (startLoc.isInvalid())
return;
auto endLoc = E->getEndLoc();
assert(endLoc.isValid());
endLoc = Lexer::getLocForEndOfToken(SM, endLoc);
auto bufferId = SM.findBufferContainingLoc(startLoc);
auto length = SM.getByteDistance(startLoc, endLoc);
auto file = SM.getIdentifierForBuffer(bufferId);
auto offset = SM.getLocOffsetInBuffer(startLoc, bufferId);
allConsts.emplace_back(file, kind, offset, length, Value);
}

std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
if (E->isSemanticallyConstExpr()) {
if (auto *SL = dyn_cast<StringLiteralExpr>(E)) {
record(SL, ConstKind::String, SL->getValue());
}
}
return { true, E };
}
public:
ConstExtractor(SDKContext &SCtx, ASTContext &Ctx): SCtx(SCtx), Ctx(Ctx),
SM(Ctx.SourceMgr) {}
void extract(ModuleDecl *MD) { MD->walk(*this); }
std::vector<ConstExprInfo> &getAllConstValues() { return allConsts; }
};
} // End of anonymous namespace

template <> struct swift::json::ObjectTraits<ConstExprInfo> {
static void mapping(Output &out, ConstExprInfo &info) {
out.mapRequired("filePath", info.filePath);
StringRef kind;
switch(info.kind) {
#define CASE(X) case ConstKind::X: kind = #X; break;
CASE(String)
CASE(Int)
#undef CASE
}
out.mapRequired("kind", kind);
out.mapRequired("offset", info.offset);
out.mapRequired("length", info.length);
out.mapRequired("value", info.value);
}
};

struct swift::ide::api::PayLoad {
std::vector<ConstExprInfo> *allContsValues = nullptr;
};

// Construct all roots vector from a given file where a forest was
// previously dumped.
void SwiftDeclCollector::deSerialize(StringRef Filename) {
Expand All @@ -2225,20 +2301,24 @@ void SwiftDeclCollector::deSerialize(StringRef Filename) {
}

// Serialize the content of all roots to a given file using JSON format.
void SwiftDeclCollector::serialize(StringRef Filename, SDKNode *Root) {
void SwiftDeclCollector::serialize(StringRef Filename, SDKNode *Root,
PayLoad OtherInfo) {
std::error_code EC;
llvm::raw_fd_ostream fs(Filename, EC, llvm::sys::fs::OF_None);
json::Output yout(fs);
assert(Root->getKind() == SDKNodeKind::Root);
SDKNodeRoot &root = *static_cast<SDKNodeRoot*>(Root);
yout.beginObject();
yout.mapRequired(ABIRootKey, root);
if (auto *constValues = OtherInfo.allContsValues) {
yout.mapRequired(ConstValuesKey, *constValues);
}
yout.endObject();
}

// Serialize the content of all roots to a given file using JSON format.
void SwiftDeclCollector::serialize(StringRef Filename) {
SwiftDeclCollector::serialize(Filename, RootNode);
SwiftDeclCollector::serialize(Filename, RootNode, PayLoad());
}

SDKNodeRoot *
Expand Down Expand Up @@ -2304,16 +2384,21 @@ swift::ide::api::getSDKNodeRoot(SDKContext &SDKCtx,
return Collector.getSDKRoot();
}

void swift::ide::api::dumpSDKRoot(SDKNodeRoot *Root, StringRef OutputFile) {
void swift::ide::api::dumpSDKRoot(SDKNodeRoot *Root, PayLoad load,
StringRef OutputFile) {
assert(Root);
auto Opts = Root->getSDKContext().getOpts();
if (Opts.Verbose)
llvm::errs() << "Dumping SDK...\n";
SwiftDeclCollector::serialize(OutputFile, Root);
SwiftDeclCollector::serialize(OutputFile, Root, load);
if (Opts.Verbose)
llvm::errs() << "Dumped to "<< OutputFile << "\n";
}

void swift::ide::api::dumpSDKRoot(SDKNodeRoot *Root, StringRef OutputFile) {
dumpSDKRoot(Root, PayLoad(), OutputFile);
}

int swift::ide::api::dumpSDKContent(const CompilerInvocation &InitInvok,
const llvm::StringSet<> &ModuleNames,
StringRef OutputFile, CheckerOptions Opts) {
Expand Down Expand Up @@ -2356,7 +2441,11 @@ void swift::ide::api::dumpModuleContent(ModuleDecl *MD, StringRef OutputFile,
SDKContext ctx(opts);
SwiftDeclCollector collector(ctx);
collector.lookupVisibleDecls({MD});
dumpSDKRoot(collector.getSDKRoot(), OutputFile);
ConstExtractor extractor(ctx, MD->getASTContext());
extractor.extract(MD);
PayLoad payload;
payload.allContsValues = &extractor.getAllConstValues();
dumpSDKRoot(collector.getSDKRoot(), payload, OutputFile);
}

int swift::ide::api::findDeclUsr(StringRef dumpPath, CheckerOptions Opts) {
Expand Down
13 changes: 13 additions & 0 deletions test/api-digester/const_values.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// RUN: %empty-directory(%t)

// RUN: echo "public func foo() { let a = \"abc\" }" > %t/t1.swift
// RUN: echo "public func bar() { let a = \"def\" }" > %t/t2.swift
// RUN: %target-swift-frontend -emit-module %t/t1.swift %t/t2.swift -o %t/Foo.swiftmodule -emit-abi-descriptor-path %t/abi.json
// RUN: %api-digester -deserialize-sdk -input-paths %t/abi.json -o %t.result

// RUN: %FileCheck %s < %t/abi.json

// CHECK: "kind": "String"
// CHECK: "value": "abc"
// CHECK: "kind": "String"
// CHECK: "value": "def"