1 /**
2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "libpandafile/bytecode_instruction.h"
17 #include "libpandafile/bytecode_instruction-inl.h"
18 #include "libpandafile/file_items.h"
19 #include "libpandafile/debug_info_updater-inl.h"
20
21 #include "linker_context.h"
22
23 namespace ark::static_linker {
24
25 namespace {
26
27 class LinkerDebugInfoUpdater : public panda_file::DebugInfoUpdater<LinkerDebugInfoUpdater> {
28 public:
29 using Super = panda_file::DebugInfoUpdater<LinkerDebugInfoUpdater>;
30
LinkerDebugInfoUpdater(const panda_file::File * file,panda_file::ItemContainer * cont)31 LinkerDebugInfoUpdater(const panda_file::File *file, panda_file::ItemContainer *cont) : Super(file), cont_(cont) {}
32
GetOrCreateStringItem(const std::string & s)33 panda_file::StringItem *GetOrCreateStringItem(const std::string &s)
34 {
35 return cont_->GetOrCreateStringItem(s);
36 }
37
GetType(panda_file::File::EntityId typeId,const std::string & typeName)38 panda_file::BaseClassItem *GetType([[maybe_unused]] panda_file::File::EntityId typeId, const std::string &typeName)
39 {
40 auto *cm = cont_->GetClassMap();
41 auto iter = cm->find(typeName);
42 if (iter != cm->end()) {
43 return iter->second;
44 }
45 return nullptr;
46 }
47
48 private:
49 panda_file::ItemContainer *cont_;
50 };
51
52 class LinkerDebugInfoScrapper : public panda_file::DebugInfoUpdater<LinkerDebugInfoScrapper> {
53 public:
54 using Super = panda_file::DebugInfoUpdater<LinkerDebugInfoScrapper>;
55
LinkerDebugInfoScrapper(const panda_file::File * file,CodePatcher * patcher,panda_file::ItemContainer * cont)56 LinkerDebugInfoScrapper(const panda_file::File *file, CodePatcher *patcher, panda_file::ItemContainer *cont)
57 : Super(file), patcher_(patcher), cont_(cont)
58 {
59 }
60
GetOrCreateStringItem(const std::string & s)61 panda_file::StringItem *GetOrCreateStringItem(const std::string &s)
62 {
63 patcher_->Add(s);
64 return nullptr;
65 }
66
GetType(panda_file::File::EntityId typeId,const std::string & typeName)67 panda_file::BaseClassItem *GetType([[maybe_unused]] panda_file::File::EntityId typeId, const std::string &typeName)
68 {
69 auto *cm = cont_->GetClassMap();
70 if (cm->find(typeName) == cm->end()) {
71 // This action must create a string for primitive types.
72 GetOrCreateStringItem(typeName);
73 }
74 return nullptr;
75 }
76
77 private:
78 CodePatcher *patcher_;
79 panda_file::ItemContainer *cont_;
80 };
81 } // namespace
82
Add(Change c)83 void CodePatcher::Add(Change c)
84 {
85 changes_.emplace_back(std::move(c));
86 }
87
Devour(CodePatcher && p)88 void CodePatcher::Devour(CodePatcher &&p)
89 {
90 const auto oldSize = changes_.size();
91 changes_.insert(changes_.end(), std::move_iterator(p.changes_.begin()), std::move_iterator(p.changes_.end()));
92 const auto newSize = changes_.size();
93 p.changes_.clear();
94
95 ranges_.emplace_back(oldSize, newSize);
96 }
97
AddRange(std::pair<size_t,size_t> range)98 void CodePatcher::AddRange(std::pair<size_t, size_t> range)
99 {
100 ranges_.push_back(range);
101 }
102
ApplyLiteralArrayChange(LiteralArrayChange & lc,Context * ctx)103 void CodePatcher::ApplyLiteralArrayChange(LiteralArrayChange &lc, Context *ctx)
104 {
105 auto id = ctx->literalArrayId_++;
106 lc.it = ctx->GetContainer().GetOrCreateLiteralArrayItem(std::to_string(id));
107
108 auto &oldIts = lc.old->GetItems();
109 auto newIts = std::vector<panda_file::LiteralItem>();
110 newIts.reserve(oldIts.size());
111
112 for (const auto &i : oldIts) {
113 using LIT = panda_file::LiteralItem::Type;
114 switch (i.GetType()) {
115 case LIT::B1:
116 case LIT::B2:
117 case LIT::B4:
118 case LIT::B8:
119 newIts.emplace_back(i);
120 break;
121 case LIT::STRING: {
122 auto str = ctx->StringFromOld(i.GetValue<panda_file::StringItem *>());
123 newIts.emplace_back(str);
124 break;
125 }
126 case LIT::METHOD: {
127 auto meth = i.GetValue<panda_file::MethodItem *>();
128 auto iter = ctx->knownItems_.find(meth);
129 ASSERT(iter != ctx->knownItems_.end());
130 ASSERT(iter->second->GetItemType() == panda_file::ItemTypes::METHOD_ITEM);
131 newIts.emplace_back(static_cast<panda_file::MethodItem *>(iter->second));
132 break;
133 }
134 default:
135 UNREACHABLE();
136 }
137 }
138
139 lc.it->AddItems(newIts);
140 }
141
ApplyDeps(Context * ctx)142 void CodePatcher::ApplyDeps(Context *ctx)
143 {
144 for (auto &v : changes_) {
145 std::visit(
146 [this, ctx](auto &a) {
147 using T = std::remove_cv_t<std::remove_reference_t<decltype(a)>>;
148 // IndexedChange, StringChange, LiteralArrayChange, std::string, std::function<bool(bool)>>
149 if constexpr (std::is_same_v<T, IndexedChange>) {
150 a.mi->AddIndexDependency(a.it);
151 } else if constexpr (std::is_same_v<T, StringChange>) {
152 a.it = ctx->GetContainer().GetOrCreateStringItem(a.str);
153 } else if constexpr (std::is_same_v<T, LiteralArrayChange>) {
154 ApplyLiteralArrayChange(a, ctx);
155 } else if constexpr (std::is_same_v<T, std::string>) {
156 // unreferenced string item should be mark
157 auto stringItem = ctx->GetContainer().GetOrCreateStringItem(a);
158 stringItem->SetDependencyMark();
159 } else if constexpr (std::is_same_v<T, std::function<bool(bool)>>) {
160 // nothing
161 } else {
162 UNREACHABLE();
163 }
164 },
165 v);
166 }
167 }
168
TryDeletePatch()169 void CodePatcher::TryDeletePatch()
170 {
171 auto markDependency = [](auto &a, bool &shouldDelete) {
172 using T = std::remove_cv_t<std::remove_reference_t<decltype(a)>>;
173 if constexpr (std::is_same_v<T, IndexedChange>) {
174 if (!a.mi->GetDependencyMark()) {
175 shouldDelete = true;
176 }
177 } else if constexpr (std::is_same_v<T, StringChange>) {
178 if (!a.mi->GetDependencyMark()) {
179 shouldDelete = true;
180 }
181 } else if constexpr (std::is_same_v<T, std::function<bool(bool)>>) {
182 if (a(true)) {
183 shouldDelete = true;
184 }
185 }
186 };
187
188 for (auto it = changes_.begin(); it != changes_.end();) {
189 auto &v = *it;
190 bool shouldDelete = false;
191 std::visit([&](auto &a) { markDependency(a, shouldDelete); }, v);
192 if (shouldDelete) {
193 it = changes_.erase(it);
194 } else {
195 ++it;
196 }
197 }
198 }
199
Patch(const std::pair<size_t,size_t> range)200 void CodePatcher::Patch(const std::pair<size_t, size_t> range)
201 {
202 for (size_t i = range.first; i < range.second; i++) {
203 auto &v = changes_[i];
204 std::visit(
205 [](auto &a) {
206 using T = std::remove_cv_t<std::remove_reference_t<decltype(a)>>;
207 if constexpr (std::is_same_v<T, IndexedChange>) {
208 auto idx = a.it->GetIndex(a.mi);
209 a.inst.UpdateId(BytecodeId(idx));
210 } else if constexpr (std::is_same_v<T, StringChange>) {
211 auto off = a.it->GetOffset();
212 a.inst.UpdateId(BytecodeId(off));
213 } else if constexpr (std::is_same_v<T, LiteralArrayChange>) {
214 auto off = a.it->GetIndex();
215 a.inst.UpdateId(BytecodeId(off));
216 } else if constexpr (std::is_same_v<T, std::string>) {
217 // nothing
218 } else if constexpr (std::is_same_v<T, std::function<bool(bool)>>) {
219 a(false);
220 } else {
221 UNREACHABLE();
222 }
223 },
224 v);
225 }
226 }
227
AddStringDependency()228 void CodePatcher::AddStringDependency()
229 {
230 auto markStringDependency = [](auto &a) {
231 using T = std::remove_cv_t<std::remove_reference_t<decltype(a)>>;
232 if constexpr (std::is_same_v<T, StringChange>) {
233 if (a.mi->GetDependencyMark()) {
234 a.it->SetDependencyMark();
235 }
236 }
237 };
238
239 for (auto it = changes_.begin(); it != changes_.end();) {
240 auto &v = *it;
241 std::visit(markStringDependency, v);
242 ++it;
243 }
244 }
245
HandleStringId(CodePatcher & p,const BytecodeInstruction & inst,const panda_file::File * filePtr,CodeData * data)246 void Context::HandleStringId(CodePatcher &p, const BytecodeInstruction &inst, const panda_file::File *filePtr,
247 CodeData *data)
248 {
249 BytecodeId bId = inst.GetId();
250 auto oldId = bId.AsFileId();
251 auto sData = filePtr->GetStringData(oldId);
252 auto itemStr = std::string(utf::Mutf8AsCString(sData.data));
253 p.Add(CodePatcher::StringChange {inst, std::move(itemStr), data->nmi});
254 }
255
HandleLiteralArrayId(CodePatcher & p,const BytecodeInstruction & inst,const panda_file::File * filePtr,const std::map<panda_file::File::EntityId,panda_file::BaseItem * > * items)256 void Context::HandleLiteralArrayId(CodePatcher &p, const BytecodeInstruction &inst, const panda_file::File *filePtr,
257 const std::map<panda_file::File::EntityId, panda_file::BaseItem *> *items)
258 {
259 BytecodeId bId = inst.GetId();
260 auto oldIdx = bId.AsRawValue();
261 auto arrs = filePtr->GetLiteralArrays();
262 ASSERT(oldIdx < arrs.size());
263 auto off = arrs[oldIdx];
264 auto iter = items->find(panda_file::File::EntityId(off));
265 ASSERT(iter != items->end());
266 ASSERT(iter->second->GetItemType() == panda_file::ItemTypes::LITERAL_ARRAY_ITEM);
267 p.Add(CodePatcher::LiteralArrayChange {inst, static_cast<panda_file::LiteralArrayItem *>(iter->second)});
268 }
269
MakeChangeWithId(CodePatcher & p,CodeData * data)270 void Context::MakeChangeWithId(CodePatcher &p, CodeData *data)
271 {
272 using Flags = ark::BytecodeInst<ark::BytecodeInstMode::FAST>::Flags;
273 using EntityId = panda_file::File::EntityId;
274
275 const auto myId = EntityId(data->omi->GetOffset());
276 auto *items = data->fileReader->GetItems();
277 auto inst = BytecodeInstruction(data->code->data());
278 size_t offset = 0;
279 const auto limit = data->code->size();
280
281 auto filePtr = data->fileReader->GetFilePtr();
282
283 using Resolver = EntityId (panda_file::File::*)(EntityId id, panda_file::File::Index idx) const;
284
285 auto makeWithId = [&p, &inst, &filePtr, &items, &myId, &data, this](Resolver resolve) {
286 auto idx = inst.GetId().AsIndex();
287 auto oldId = (filePtr->*resolve)(myId, idx);
288 auto iter = items->find(oldId);
289 ASSERT(iter != items->end());
290 auto asIndexed = static_cast<panda_file::IndexedItem *>(iter->second);
291 auto found = knownItems_.find(asIndexed);
292 ASSERT(found != knownItems_.end());
293 p.Add(CodePatcher::IndexedChange {inst, data->nmi, static_cast<panda_file::IndexedItem *>(found->second)});
294 };
295
296 while (offset < limit) {
297 if (offset + inst.GetSize() > limit) {
298 LOG(FATAL, STATIC_LINKER) << "Invalid code size: " << limit << ", offset: " << offset
299 << ", instruction size: " << inst.GetSize();
300 break;
301 }
302 if (inst.HasFlag(Flags::TYPE_ID)) {
303 makeWithId(&panda_file::File::ResolveClassIndex);
304 // inst.UpdateId()
305 } else if (inst.HasFlag(Flags::METHOD_ID) || inst.HasFlag(Flags::STATIC_METHOD_ID)) {
306 makeWithId(&panda_file::File::ResolveMethodIndex);
307 } else if (inst.HasFlag(Flags::FIELD_ID) || inst.HasFlag(Flags::STATIC_FIELD_ID)) {
308 makeWithId(&panda_file::File::ResolveFieldIndex);
309 } else if (inst.HasFlag(Flags::STRING_ID)) {
310 HandleStringId(p, inst, filePtr, data);
311 } else if (inst.HasFlag(Flags::LITERALARRAY_ID)) {
312 HandleLiteralArrayId(p, inst, filePtr, items);
313 }
314
315 offset += inst.GetSize();
316 inst = inst.GetNext();
317 }
318 if (offset != limit) {
319 LOG(FATAL, STATIC_LINKER) << "Code size mismatch: expected " << limit << ", got " << offset;
320 }
321 }
322
ProcessCodeData(CodePatcher & p,CodeData * data)323 void Context::ProcessCodeData(CodePatcher &p, CodeData *data)
324 {
325 if (data->code != nullptr) {
326 MakeChangeWithId(p, data);
327 }
328
329 auto *dbg = data->omi->GetDebugInfo();
330 if (dbg == nullptr || conf_.stripDebugInfo) {
331 return;
332 }
333
334 // Collect string items for each method with debug information.
335 auto file = data->fileReader->GetFilePtr();
336 auto scrapper = LinkerDebugInfoScrapper(file, &p, &cont_);
337 auto off = dbg->GetOffset();
338 ASSERT(off != 0);
339 auto eId = panda_file::File::EntityId(off);
340 scrapper.Scrap(eId);
341
342 auto newDbg = data->nmi->GetDebugInfo();
343 p.Add([file, this, newDbg, eId, patchLnp = data->patchLnp, nmi = data->nmi](bool peek) -> bool {
344 if (peek) {
345 // peek won't patch lnp, only return method mark for delete judge
346 return !nmi->GetDependencyMark();
347 }
348 auto updater = LinkerDebugInfoUpdater(file, &cont_);
349
350 auto *constantPool = newDbg->GetConstantPool();
351 if (patchLnp) {
352 // Original `LineNumberProgram` - must emit both instructions and their arguments.
353 auto *lnpItem = newDbg->GetLineNumberProgram();
354 updater.Emit(lnpItem, constantPool, eId);
355 } else {
356 auto *lnpItemold = newDbg->GetLineNumberProgram();
357 if (lnpItemold->GetData().empty()) {
358 // emit if lnp size zero after delete item
359 auto *lnpItem = newDbg->GetLineNumberProgram();
360 updater.Emit(lnpItem, constantPool, eId);
361 } else {
362 // `LineNumberProgram` is reused and its instructions will be emitted by owner-method.
363 // Still need to emit instructions' arguments, which are unique for each method.
364 panda_file::LineNumberProgramItemBase lnpItem;
365 updater.Emit(&lnpItem, constantPool, eId);
366 }
367 }
368 return false;
369 });
370 }
371
372 } // namespace ark::static_linker
373