Skip to content

Commit 4e50caa

Browse files
committed
Provide users a way to record ObjC methods referenced by message sends
and emit the information to a file in JSON format Environmental variable CLANG_COMPILER_OBJC_MESSAGE_TRACE_PATH must be set to enable this.
1 parent 59c3c42 commit 4e50caa

File tree

7 files changed

+348
-0
lines changed

7 files changed

+348
-0
lines changed

clang/include/clang/AST/ASTContext.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,24 @@ class ASTContext : public RefCountedBase<ASTContext> {
490490

491491
ASTContext &this_() { return *this; }
492492

493+
mutable std::optional<std::string> ObjCMsgSendUsageFile;
494+
llvm::SmallVector<const ObjCMethodDecl *, 4> ObjCMsgSendUsage;
495+
496+
public:
497+
/// Check whether env variable CLANG_COMPILER_OBJC_MESSAGE_TRACE_PATH is set.
498+
/// If it is set, assign the value to ObjCMsgSendUsageFile.
499+
bool isObjCMsgSendUsageFileSpecified() const;
500+
501+
/// Return the file name stored in ObjCMsgSendUsageFile if it has a value,
502+
/// return an empty string otherwise.
503+
std::string getObjCMsgSendUsageFilename() const;
504+
505+
/// Record an ObjC method.
506+
void recordObjCMsgSendUsage(const ObjCMethodDecl *Method);
507+
508+
/// Write the collected ObjC method tracing information to a file.
509+
void writeObjCMsgSendUsages(const std::string &Filename);
510+
493511
public:
494512
/// A type synonym for the TemplateOrInstantiation mapping.
495513
using TemplateOrSpecializationInfo =
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===- ObjcMethodReferenceInfo.h - API for ObjC method tracing --*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file defines APIs for ObjC method tracing.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef LLVM_CLANG_OBJC_METHOD_REFERENCE_INFO_H
14+
#define LLVM_CLANG_OBJC_METHOD_REFERENCE_INFO_H
15+
16+
#include "llvm/ADT/SmallVector.h"
17+
#include "llvm/Support/raw_ostream.h"
18+
#include <map>
19+
#include <string>
20+
21+
namespace clang {
22+
23+
class ObjCMethodDecl;
24+
25+
struct ObjCMethodReferenceInfo {
26+
static constexpr unsigned FormatVersion = 1;
27+
std::string Target, TargetVariant;
28+
29+
/// Paths to the files in which ObjC methods are referenced.
30+
llvm::SmallVector<std::string, 4> FilePaths;
31+
32+
/// A map from the index of a file in FilePaths to the list of ObjC methods.
33+
std::map<unsigned, llvm::SmallVector<const ObjCMethodDecl *, 4>> References;
34+
};
35+
36+
/// This function serializes the ObjC message tracing information in JSON.
37+
void serializeObjCMethodReferencesAsJson(const ObjCMethodReferenceInfo &Info,
38+
llvm::raw_ostream &OS);
39+
40+
} // namespace clang
41+
42+
#endif // LLVM_CLANG_OBJC_METHOD_REFERENCE_INFO_H

clang/lib/AST/ASTContext.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "clang/AST/Mangle.h"
3838
#include "clang/AST/MangleNumberingContext.h"
3939
#include "clang/AST/NestedNameSpecifier.h"
40+
#include "clang/AST/ObjCMethodReferenceInfo.h"
4041
#include "clang/AST/ParentMapContext.h"
4142
#include "clang/AST/RawCommentList.h"
4243
#include "clang/AST/RecordLayout.h"
@@ -85,6 +86,7 @@
8586
#include "llvm/Support/Casting.h"
8687
#include "llvm/Support/Compiler.h"
8788
#include "llvm/Support/ErrorHandling.h"
89+
#include "llvm/Support/JSON.h"
8890
#include "llvm/Support/MD5.h"
8991
#include "llvm/Support/MathExtras.h"
9092
#include "llvm/Support/SipHash.h"
@@ -9241,6 +9243,133 @@ static TypedefDecl *CreateVoidPtrBuiltinVaListDecl(const ASTContext *Context) {
92419243
return Context->buildImplicitTypedef(T, "__builtin_va_list");
92429244
}
92439245

9246+
bool ASTContext::isObjCMsgSendUsageFileSpecified() const {
9247+
if (!ObjCMsgSendUsageFile) {
9248+
if (const char *Filename =
9249+
std::getenv("CLANG_COMPILER_OBJC_MESSAGE_TRACE_PATH"))
9250+
ObjCMsgSendUsageFile = Filename;
9251+
else
9252+
return false;
9253+
}
9254+
9255+
return true;
9256+
}
9257+
9258+
std::string ASTContext::getObjCMsgSendUsageFilename() const {
9259+
if (isObjCMsgSendUsageFileSpecified())
9260+
return *ObjCMsgSendUsageFile;
9261+
return "";
9262+
}
9263+
9264+
void ASTContext::recordObjCMsgSendUsage(const ObjCMethodDecl *Method) {
9265+
ObjCMsgSendUsage.push_back(Method);
9266+
}
9267+
9268+
static StringRef selectMethodKey(const clang::ObjCMethodDecl *clangD) {
9269+
assert(clangD);
9270+
if (clangD->isInstanceMethod())
9271+
return "instance_method";
9272+
assert(clangD->isClassMethod() && "must be a class method");
9273+
return "class_method";
9274+
}
9275+
9276+
static std::array<std::pair<StringRef, StringRef>, 2>
9277+
selectMethodOwnerKey(const clang::NamedDecl *clangD) {
9278+
assert(clangD);
9279+
if (isa<clang::ObjCInterfaceDecl>(clangD))
9280+
return {{{"interface_type", clangD->getName()}, {}}};
9281+
if (isa<clang::ObjCImplementationDecl>(clangD))
9282+
return {{{"implementation_type", clangD->getName()}, {}}};
9283+
if (auto *Cat = dyn_cast<clang::ObjCCategoryDecl>(clangD))
9284+
return {{{"interface_type", Cat->getClassInterface()->getName()},
9285+
{"category_type", Cat->getName()}}};
9286+
if (auto *Cat = dyn_cast<clang::ObjCCategoryImplDecl>(clangD))
9287+
return {{{"interface_type",
9288+
Cat->getCategoryDecl()->getClassInterface()->getName()},
9289+
{"category_implementation_type", Cat->getName()}}};
9290+
if (isa<clang::ObjCProtocolDecl>(clangD))
9291+
return {{{"protocol_type", clangD->getName()}, {}}};
9292+
llvm_unreachable("unknown method owner");
9293+
}
9294+
9295+
void clang::serializeObjCMethodReferencesAsJson(
9296+
const clang::ObjCMethodReferenceInfo &Info, llvm::raw_ostream &OS) {
9297+
llvm::json::OStream Out(OS, /*IndentSize=*/4);
9298+
Out.object([&] {
9299+
Out.attribute("format-version", Info.FormatVersion);
9300+
Out.attribute("target", Info.Target);
9301+
if (!Info.TargetVariant.empty())
9302+
Out.attribute("target-variant", Info.TargetVariant);
9303+
Out.attributeArray("references", [&] {
9304+
for (auto &Ref : Info.References) {
9305+
unsigned FileID = Ref.first;
9306+
for (const clang::ObjCMethodDecl *clangD : Ref.second) {
9307+
auto &SM = clangD->getASTContext().getSourceManager();
9308+
clang::SourceLocation Loc = clangD->getLocation();
9309+
if (!Loc.isValid())
9310+
continue;
9311+
Out.object([&] {
9312+
if (auto *parent =
9313+
dyn_cast_or_null<clang::NamedDecl>(clangD->getParent())) {
9314+
std::array<std::pair<StringRef, StringRef>, 2> OwnerInfo =
9315+
selectMethodOwnerKey(parent);
9316+
for (auto I : OwnerInfo)
9317+
if (I.first.data())
9318+
Out.attribute(I.first, I.second);
9319+
}
9320+
9321+
std::string MangledMethodName;
9322+
llvm::raw_string_ostream Out2(MangledMethodName);
9323+
std::unique_ptr<MangleContext> Mangler(
9324+
clangD->getASTContext().createMangleContext());
9325+
Mangler->mangleObjCMethodName(clangD, Out2,
9326+
/*includePrefixByte=*/false, true);
9327+
Out.attribute(selectMethodKey(clangD), MangledMethodName);
9328+
Out.attribute("declared_at", Loc.printToString(SM));
9329+
Out.attribute("referenced_at_file_id", FileID);
9330+
});
9331+
}
9332+
}
9333+
});
9334+
9335+
Out.attributeArray("fileMap", [&] {
9336+
for (unsigned I = 0, N = Info.FilePaths.size(); I != N; I++) {
9337+
Out.object([&] {
9338+
Out.attribute("file_id", I + 1);
9339+
Out.attribute("file_path", Info.FilePaths[I]);
9340+
});
9341+
}
9342+
});
9343+
});
9344+
}
9345+
9346+
void ASTContext::writeObjCMsgSendUsages(const std::string &Filename) {
9347+
std::error_code EC;
9348+
auto FDS = std::make_unique<llvm::raw_fd_ostream>(
9349+
Filename, EC, llvm::sys::fs::OF_Text | llvm::sys::fs::OF_Append);
9350+
if (EC) {
9351+
unsigned ID = getDiagnostics().getCustomDiagID(
9352+
DiagnosticsEngine::Error,
9353+
"couldn't open objc message send tracing file %0");
9354+
getDiagnostics().Report(ID) << Filename;
9355+
return;
9356+
}
9357+
9358+
if (auto L = FDS->lock()) {
9359+
clang::ObjCMethodReferenceInfo Info;
9360+
OptionalFileEntryRef FE =
9361+
SourceMgr.getFileEntryRefForID(SourceMgr.getMainFileID());
9362+
SmallString<256> MainFile(FE->getName());
9363+
SourceMgr.getFileManager().makeAbsolutePath(MainFile);
9364+
Info.Target = getTargetInfo().getTriple().str();
9365+
if (auto *VariantTriple = getTargetInfo().getDarwinTargetVariantTriple())
9366+
Info.TargetVariant = VariantTriple->str();
9367+
Info.FilePaths.push_back(MainFile.c_str());
9368+
Info.References[1] = ObjCMsgSendUsage;
9369+
serializeObjCMethodReferencesAsJson(Info, *FDS);
9370+
}
9371+
}
9372+
92449373
static TypedefDecl *
92459374
CreateAArch64ABIBuiltinVaListDecl(const ASTContext *Context) {
92469375
// struct __va_list

clang/lib/Sema/Sema.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,6 +1202,10 @@ void Sema::ActOnEndOfTranslationUnit() {
12021202
}
12031203
}
12041204

1205+
if (getASTContext().isObjCMsgSendUsageFileSpecified())
1206+
getASTContext().writeObjCMsgSendUsages(
1207+
getASTContext().getObjCMsgSendUsageFilename());
1208+
12051209
DiagnoseUnterminatedPragmaAlignPack();
12061210
DiagnoseUnterminatedPragmaAttribute();
12071211
OpenMP().DiagnoseUnterminatedOpenMPDeclareTarget();

clang/lib/Sema/SemaExprObjC.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,6 +2739,10 @@ ExprResult SemaObjC::BuildClassMessage(
27392739
if (!isImplicit)
27402740
checkCocoaAPI(SemaRef, Result);
27412741
}
2742+
2743+
if (Method && Context.isObjCMsgSendUsageFileSpecified())
2744+
Context.recordObjCMsgSendUsage(Method);
2745+
27422746
if (Method)
27432747
checkFoundationAPI(SemaRef, SelLoc, Method, ArrayRef(Args, NumArgs),
27442748
ReceiverType, /*IsClassObjectCall=*/true);
@@ -3330,6 +3334,10 @@ ExprResult SemaObjC::BuildInstanceMessage(
33303334
if (!isImplicit)
33313335
checkCocoaAPI(SemaRef, Result);
33323336
}
3337+
3338+
if (Method && Context.isObjCMsgSendUsageFileSpecified())
3339+
Context.recordObjCMsgSendUsage(Method);
3340+
33333341
if (Method) {
33343342
bool IsClassObjectCall = ClassMessage;
33353343
// 'self' message receivers in class methods should be treated as message
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@interface A
2+
-(void)m0;
3+
@end
4+
5+
@interface B
6+
-(void)m0;
7+
+(void)m1;
8+
@end
9+
10+
#define CDef \
11+
@interface C \
12+
-(void)m4; \
13+
@end
14+
15+
CDef
16+
17+
@protocol P0
18+
-(void)m5;
19+
@end

clang/test/AST/objc-method-tracing.mm

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// RUN: env CLANG_COMPILER_OBJC_MESSAGE_TRACE_PATH=%t.txt %clang_cc1 -fsyntax-only -triple arm64-apple-macosx15.0.0 -I %S/Inputs %s
2+
// RUN: cat %t.txt | FileCheck %s
3+
4+
// CHECK: {
5+
// CHECK-NEXT: "format-version": 1,
6+
// CHECK-NEXT: "target": "arm64-apple-macosx15.0.0",
7+
// CHECK-NEXT: "references": [
8+
// CHECK-NEXT: {
9+
// CHECK-NEXT: "interface_type": "A",
10+
// CHECK-NEXT: "instance_method": "-[A m0]",
11+
// CHECK-NEXT: "declared_at": "[[HEADER_FILE:.*]]:2:1",
12+
// CHECK-NEXT: "referenced_at_file_id": 1
13+
// CHECK-NEXT: },
14+
// CHECK-NEXT: {
15+
// CHECK-NEXT: "interface_type": "B",
16+
// CHECK-NEXT: "instance_method": "-[B m0]",
17+
// CHECK-NEXT: "declared_at": "[[HEADER_FILE]]:6:1",
18+
// CHECK-NEXT: "referenced_at_file_id": 1
19+
// CHECK-NEXT: },
20+
// CHECK-NEXT: {
21+
// CHECK-NEXT: "interface_type": "B",
22+
// CHECK-NEXT: "class_method": "+[B m1]",
23+
// CHECK-NEXT: "declared_at": "[[HEADER_FILE]]:7:1",
24+
// CHECK-NEXT: "referenced_at_file_id": 1
25+
// CHECK-NEXT: },
26+
// CHECK-NEXT: {
27+
// CHECK-NEXT: "interface_type": "B",
28+
// CHECK-NEXT: "category_type": "Cat1",
29+
// CHECK-NEXT: "instance_method": "-[B(Cat1) m2]",
30+
// CHECK-NEXT: "declared_at": "[[SOURCE_FILE:.*]]:12:1",
31+
// CHECK-NEXT: "referenced_at_file_id": 1
32+
// CHECK-NEXT: },
33+
// CHECK-NEXT: {
34+
// CHECK-NEXT: "interface_type": "C",
35+
// CHECK-NEXT: "instance_method": "-[C m4]",
36+
// CHECK-NEXT: "declared_at": "[[HEADER_FILE]]:15:1 <Spelling=[[HEADER_FILE]]:11:14>",
37+
// CHECK-NEXT: "referenced_at_file_id": 1
38+
// CHECK-NEXT: },
39+
// CHECK-NEXT: {
40+
// CHECK-NEXT: "protocol_type": "P0",
41+
// CHECK-NEXT: "instance_method": "-[P0 m5]",
42+
// CHECK-NEXT: "declared_at": "[[HEADER_FILE]]:18:1",
43+
// CHECK-NEXT: "referenced_at_file_id": 1
44+
// CHECK-NEXT: },
45+
// CHECK-NEXT: {
46+
// CHECK-NEXT: "interface_type": "B",
47+
// CHECK-NEXT: "category_type": "",
48+
// CHECK-NEXT: "instance_method": "-[B() m6]",
49+
// CHECK-NEXT: "declared_at": "[[SOURCE_FILE]]:16:1",
50+
// CHECK-NEXT: "referenced_at_file_id": 1
51+
// CHECK-NEXT: },
52+
// CHECK-NEXT: {
53+
// CHECK-NEXT: "implementation_type": "B",
54+
// CHECK-NEXT: "instance_method": "-[B m7:arg1:]",
55+
// CHECK-NEXT: "declared_at": "[[SOURCE_FILE]]:20:1",
56+
// CHECK-NEXT: "referenced_at_file_id": 1
57+
// CHECK-NEXT: },
58+
// CHECK-NEXT: {
59+
// CHECK-NEXT: "interface_type": "B",
60+
// CHECK-NEXT: "category_implementation_type": "Cat1",
61+
// CHECK-NEXT: "instance_method": "-[B(Cat1) m8]",
62+
// CHECK-NEXT: "declared_at": "[[SOURCE_FILE]]:25:1",
63+
// CHECK-NEXT: "referenced_at_file_id": 1
64+
// CHECK-NEXT: }
65+
// CHECK-NEXT: ],
66+
// CHECK-NEXT: "fileMap": [
67+
// CHECK-NEXT: {
68+
// CHECK-NEXT: "file_id": 1,
69+
// CHECK-NEXT: "file_path": "[[SOURCE_FILE]]"
70+
// CHECK-NEXT: }
71+
// CHECK-NEXT: ]
72+
// CHECK-NEXT: }
73+
74+
#include "objc-method-tracing.h"
75+
76+
@interface B(Cat1)
77+
-(void)m2;
78+
@end
79+
80+
@interface B()
81+
-(void)m6;
82+
@end
83+
84+
@implementation B
85+
-(void)m7:(int)i arg1:(float)f {
86+
}
87+
@end
88+
89+
@implementation B(Cat1)
90+
-(void)m8 {
91+
}
92+
@end
93+
94+
void test0(A *a) {
95+
[a m0];
96+
}
97+
98+
void test1(B *b) {
99+
[b m0];
100+
}
101+
102+
void test2(B *b) {
103+
[B m1];
104+
}
105+
106+
void test3(B *b) {
107+
[b m2];
108+
}
109+
110+
void test4(C *c) {
111+
[c m4];
112+
}
113+
114+
void test5(id<P0> p0) {
115+
[p0 m5];
116+
}
117+
118+
void test6(B *b) {
119+
[b m6];
120+
}
121+
122+
void test7(B *b) {
123+
[b m7:123 arg1:4.5f];
124+
}
125+
126+
void test8(B *b) {
127+
[b m8];
128+
}

0 commit comments

Comments
 (0)