1 | //===--- InterpFrame.cpp - Call Frame implementation for the VM -*- 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 | #include "InterpFrame.h" |
10 | #include "Boolean.h" |
11 | #include "Function.h" |
12 | #include "InterpStack.h" |
13 | #include "InterpState.h" |
14 | #include "MemberPointer.h" |
15 | #include "Pointer.h" |
16 | #include "PrimType.h" |
17 | #include "Program.h" |
18 | #include "clang/AST/ASTContext.h" |
19 | #include "clang/AST/DeclCXX.h" |
20 | #include "clang/AST/ExprCXX.h" |
21 | |
22 | using namespace clang; |
23 | using namespace clang::interp; |
24 | |
25 | InterpFrame::InterpFrame(InterpState &S) |
26 | : Caller(nullptr), S(S), Depth(0), Func(nullptr), RetPC(CodePtr()), |
27 | ArgSize(0), Args(nullptr), FrameOffset(0), IsBottom(true) {} |
28 | |
29 | InterpFrame::InterpFrame(InterpState &S, const Function *Func, |
30 | InterpFrame *Caller, CodePtr RetPC, unsigned ArgSize) |
31 | : Caller(Caller), S(S), Depth(Caller ? Caller->Depth + 1 : 0), Func(Func), |
32 | RetPC(RetPC), ArgSize(ArgSize), Args(static_cast<char *>(S.Stk.top())), |
33 | FrameOffset(S.Stk.size()), IsBottom(!Caller) { |
34 | if (!Func) |
35 | return; |
36 | |
37 | unsigned FrameSize = Func->getFrameSize(); |
38 | if (FrameSize == 0) |
39 | return; |
40 | |
41 | Locals = std::make_unique<char[]>(num: FrameSize); |
42 | for (auto &Scope : Func->scopes()) { |
43 | for (auto &Local : Scope.locals()) { |
44 | new (localBlock(Offset: Local.Offset)) Block(S.Ctx.getEvalID(), Local.Desc); |
45 | // Note that we are NOT calling invokeCtor() here, since that is done |
46 | // via the InitScope op. |
47 | new (localInlineDesc(Offset: Local.Offset)) InlineDescriptor(Local.Desc); |
48 | } |
49 | } |
50 | } |
51 | |
52 | InterpFrame::InterpFrame(InterpState &S, const Function *Func, CodePtr RetPC, |
53 | unsigned VarArgSize) |
54 | : InterpFrame(S, Func, S.Current, RetPC, Func->getArgSize() + VarArgSize) { |
55 | // As per our calling convention, the this pointer is |
56 | // part of the ArgSize. |
57 | // If the function has RVO, the RVO pointer is first. |
58 | // If the fuction has a This pointer, that one is next. |
59 | // Then follow the actual arguments (but those are handled |
60 | // in getParamPointer()). |
61 | if (Func->hasRVO()) |
62 | RVOPtr = stackRef<Pointer>(Offset: 0); |
63 | |
64 | if (Func->hasThisPointer()) { |
65 | if (Func->hasRVO()) |
66 | This = stackRef<Pointer>(Offset: sizeof(Pointer)); |
67 | else |
68 | This = stackRef<Pointer>(Offset: 0); |
69 | } |
70 | } |
71 | |
72 | InterpFrame::~InterpFrame() { |
73 | for (auto &Param : Params) |
74 | S.deallocate(B: reinterpret_cast<Block *>(Param.second.get())); |
75 | |
76 | // When destroying the InterpFrame, call the Dtor for all block |
77 | // that haven't been destroyed via a destroy() op yet. |
78 | // This happens when the execution is interruped midway-through. |
79 | destroyScopes(); |
80 | } |
81 | |
82 | void InterpFrame::destroyScopes() { |
83 | if (!Func) |
84 | return; |
85 | for (auto &Scope : Func->scopes()) { |
86 | for (auto &Local : Scope.locals()) { |
87 | S.deallocate(B: localBlock(Offset: Local.Offset)); |
88 | } |
89 | } |
90 | } |
91 | |
92 | void InterpFrame::initScope(unsigned Idx) { |
93 | if (!Func) |
94 | return; |
95 | for (auto &Local : Func->getScope(Idx).locals()) { |
96 | localBlock(Offset: Local.Offset)->invokeCtor(); |
97 | } |
98 | } |
99 | |
100 | void InterpFrame::destroy(unsigned Idx) { |
101 | for (auto &Local : Func->getScope(Idx).locals_reverse()) { |
102 | S.deallocate(B: localBlock(Offset: Local.Offset)); |
103 | } |
104 | } |
105 | |
106 | template <typename T> |
107 | static void print(llvm::raw_ostream &OS, const T &V, ASTContext &ASTCtx, |
108 | QualType Ty) { |
109 | if constexpr (std::is_same_v<Pointer, T>) { |
110 | if (Ty->isPointerOrReferenceType()) |
111 | V.toAPValue(ASTCtx).printPretty(OS, ASTCtx, Ty); |
112 | else { |
113 | if (std::optional<APValue> RValue = V.toRValue(ASTCtx, Ty)) |
114 | RValue->printPretty(OS, Ctx: ASTCtx, Ty); |
115 | else |
116 | OS << "..." ; |
117 | } |
118 | } else { |
119 | V.toAPValue(ASTCtx).printPretty(OS, ASTCtx, Ty); |
120 | } |
121 | } |
122 | |
123 | static bool shouldSkipInBacktrace(const Function *F) { |
124 | if (F->isLambdaStaticInvoker()) |
125 | return true; |
126 | |
127 | const FunctionDecl *FD = F->getDecl(); |
128 | if (FD->getDeclName().getCXXOverloadedOperator() == OO_New || |
129 | FD->getDeclName().getCXXOverloadedOperator() == OO_Array_New) |
130 | return true; |
131 | return false; |
132 | } |
133 | |
134 | void InterpFrame::describe(llvm::raw_ostream &OS) const { |
135 | // For lambda static invokers, we would just print __invoke(). |
136 | if (const auto *F = getFunction(); F && shouldSkipInBacktrace(F)) |
137 | return; |
138 | |
139 | const Expr *CallExpr = Caller->getExpr(PC: getRetPC()); |
140 | const FunctionDecl *F = getCallee(); |
141 | bool IsMemberCall = isa<CXXMethodDecl>(Val: F) && !isa<CXXConstructorDecl>(Val: F) && |
142 | cast<CXXMethodDecl>(Val: F)->isImplicitObjectMemberFunction(); |
143 | if (Func->hasThisPointer() && IsMemberCall) { |
144 | if (const auto *MCE = dyn_cast_if_present<CXXMemberCallExpr>(Val: CallExpr)) { |
145 | const Expr *Object = MCE->getImplicitObjectArgument(); |
146 | Object->printPretty(OS, /*Helper=*/nullptr, |
147 | Policy: S.getASTContext().getPrintingPolicy(), |
148 | /*Indentation=*/0); |
149 | if (Object->getType()->isPointerType()) |
150 | OS << "->" ; |
151 | else |
152 | OS << "." ; |
153 | } else if (const auto *OCE = |
154 | dyn_cast_if_present<CXXOperatorCallExpr>(Val: CallExpr)) { |
155 | OCE->getArg(Arg: 0)->printPretty(OS, /*Helper=*/nullptr, |
156 | Policy: S.getASTContext().getPrintingPolicy(), |
157 | /*Indentation=*/0); |
158 | OS << "." ; |
159 | } else if (const auto *M = dyn_cast<CXXMethodDecl>(Val: F)) { |
160 | print(OS, V: This, ASTCtx&: S.getASTContext(), |
161 | Ty: S.getASTContext().getLValueReferenceType( |
162 | T: S.getASTContext().getRecordType(Decl: M->getParent()))); |
163 | OS << "." ; |
164 | } |
165 | } |
166 | |
167 | F->getNameForDiagnostic(OS, Policy: S.getASTContext().getPrintingPolicy(), |
168 | /*Qualified=*/false); |
169 | OS << '('; |
170 | unsigned Off = 0; |
171 | |
172 | Off += Func->hasRVO() ? primSize(Type: PT_Ptr) : 0; |
173 | Off += Func->hasThisPointer() ? primSize(Type: PT_Ptr) : 0; |
174 | |
175 | for (unsigned I = 0, N = F->getNumParams(); I < N; ++I) { |
176 | QualType Ty = F->getParamDecl(i: I)->getType(); |
177 | |
178 | PrimType PrimTy = S.Ctx.classify(T: Ty).value_or(u: PT_Ptr); |
179 | |
180 | TYPE_SWITCH(PrimTy, print(OS, stackRef<T>(Off), S.getASTContext(), Ty)); |
181 | Off += align(Size: primSize(Type: PrimTy)); |
182 | if (I + 1 != N) |
183 | OS << ", " ; |
184 | } |
185 | OS << ")" ; |
186 | } |
187 | |
188 | Frame *InterpFrame::getCaller() const { |
189 | if (Caller->Caller) |
190 | return Caller; |
191 | return S.getSplitFrame(); |
192 | } |
193 | |
194 | SourceRange InterpFrame::getCallRange() const { |
195 | if (!Caller->Func) { |
196 | if (SourceRange NullRange = S.getRange(F: nullptr, PC: {}); NullRange.isValid()) |
197 | return NullRange; |
198 | return S.EvalLocation; |
199 | } |
200 | return S.getRange(F: Caller->Func, PC: RetPC - sizeof(uintptr_t)); |
201 | } |
202 | |
203 | const FunctionDecl *InterpFrame::getCallee() const { |
204 | if (!Func) |
205 | return nullptr; |
206 | return Func->getDecl(); |
207 | } |
208 | |
209 | Pointer InterpFrame::getLocalPointer(unsigned Offset) const { |
210 | assert(Offset < Func->getFrameSize() && "Invalid local offset." ); |
211 | return Pointer(localBlock(Offset)); |
212 | } |
213 | |
214 | Pointer InterpFrame::getParamPointer(unsigned Off) { |
215 | // Return the block if it was created previously. |
216 | if (auto Pt = Params.find(Val: Off); Pt != Params.end()) |
217 | return Pointer(reinterpret_cast<Block *>(Pt->second.get())); |
218 | |
219 | // Allocate memory to store the parameter and the block metadata. |
220 | const auto &Desc = Func->getParamDescriptor(Offset: Off); |
221 | size_t BlockSize = sizeof(Block) + Desc.second->getAllocSize(); |
222 | auto Memory = std::make_unique<char[]>(num: BlockSize); |
223 | auto *B = new (Memory.get()) Block(S.Ctx.getEvalID(), Desc.second); |
224 | B->invokeCtor(); |
225 | |
226 | // Copy the initial value. |
227 | TYPE_SWITCH(Desc.first, new (B->data()) T(stackRef<T>(Off))); |
228 | |
229 | // Record the param. |
230 | Params.insert(KV: {Off, std::move(Memory)}); |
231 | return Pointer(B); |
232 | } |
233 | |
234 | static bool funcHasUsableBody(const Function *F) { |
235 | assert(F); |
236 | |
237 | if (F->isConstructor() || F->isDestructor()) |
238 | return true; |
239 | |
240 | return !F->getDecl()->isImplicit(); |
241 | } |
242 | |
243 | SourceInfo InterpFrame::getSource(CodePtr PC) const { |
244 | // Implicitly created functions don't have any code we could point at, |
245 | // so return the call site. |
246 | if (Func && !funcHasUsableBody(F: Func) && Caller) |
247 | return Caller->getSource(PC: RetPC); |
248 | |
249 | // Similarly, if the resulting source location is invalid anyway, |
250 | // point to the caller instead. |
251 | SourceInfo Result = S.getSource(F: Func, PC); |
252 | if (Result.getLoc().isInvalid() && Caller) |
253 | return Caller->getSource(PC: RetPC); |
254 | return Result; |
255 | } |
256 | |
257 | const Expr *InterpFrame::getExpr(CodePtr PC) const { |
258 | if (Func && !funcHasUsableBody(F: Func) && Caller) |
259 | return Caller->getExpr(PC: RetPC); |
260 | |
261 | return S.getExpr(F: Func, PC); |
262 | } |
263 | |
264 | SourceLocation InterpFrame::getLocation(CodePtr PC) const { |
265 | if (Func && !funcHasUsableBody(F: Func) && Caller) |
266 | return Caller->getLocation(PC: RetPC); |
267 | |
268 | return S.getLocation(F: Func, PC); |
269 | } |
270 | |
271 | SourceRange InterpFrame::getRange(CodePtr PC) const { |
272 | if (Func && !funcHasUsableBody(F: Func) && Caller) |
273 | return Caller->getRange(PC: RetPC); |
274 | |
275 | return S.getRange(F: Func, PC); |
276 | } |
277 | |
278 | bool InterpFrame::isStdFunction() const { |
279 | if (!Func) |
280 | return false; |
281 | for (const DeclContext *DC = Func->getDecl(); DC; DC = DC->getParent()) |
282 | if (DC->isStdNamespace()) |
283 | return true; |
284 | |
285 | return false; |
286 | } |
287 | |