1 /**
2 * Copyright (c) 2021-2024 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 <gtest/gtest.h>
17 #include <algorithm>
18 #include <cstddef>
19 #include <cstdlib>
20 #include <fstream>
21 #include <ios>
22 #include <memory>
23 #include <optional>
24 #include <sstream>
25 #include <string>
26
27 #include "linker_context.h"
28 #include "assembler/assembly-field.h"
29 #include "assembler/assembly-function.h"
30 #include "assembler/assembly-program.h"
31 #include "assembler/assembly-record.h"
32 #include "assembler/assembly-emitter.h"
33 #include "assembler/assembly-parser.h"
34 #include "assembler/ins_create_api.h"
35 #include "libpandafile/file_item_container.h"
36 #include "libpandafile/file_writer.h"
37 #include "include/runtime_options.h"
38 #include "linker.h"
39 #include "runtime/include/runtime.h"
40 #include "source_lang_enum.h"
41
42 namespace {
43 using Config = ark::static_linker::Config;
44 using Result = ark::static_linker::Result;
45 using ark::static_linker::DefaultConfig;
46 using ark::static_linker::Link;
47
48 constexpr size_t TEST_REPEAT_COUNT = 10;
49
ExecPanda(const std::string & file)50 std::pair<int, std::string> ExecPanda(const std::string &file)
51 {
52 auto opts = ark::RuntimeOptions {};
53 auto boot = opts.GetBootPandaFiles();
54 for (auto &a : boot) {
55 a.insert(0, "../");
56 }
57 opts.SetBootPandaFiles(std::move(boot));
58
59 opts.SetLoadRuntimes({"core"});
60
61 opts.SetPandaFiles({file});
62 if (!ark::Runtime::Create(opts)) {
63 return {1, "can't create runtime"};
64 }
65
66 auto *runtime = ark::Runtime::GetCurrent();
67
68 std::stringstream strBuf;
69 auto old = std::cout.rdbuf(strBuf.rdbuf());
70 auto reset = [&old](auto *cout) { cout->rdbuf(old); };
71 auto guard = std::unique_ptr<std::ostream, decltype(reset)>(&std::cout, reset);
72
73 auto res = runtime->ExecutePandaFile(file, "_GLOBAL::main", {});
74 auto ret = std::pair<int, std::string> {};
75 if (!res) {
76 ret = {1, "error " + std::to_string((int)res.Error())};
77 } else {
78 ret = {0, strBuf.str()};
79 }
80
81 if (!ark::Runtime::Destroy()) {
82 return {1, "can't destroy runtime"};
83 }
84
85 return ret;
86 }
87
88 template <bool IS_BINARY>
ReadFile(const std::string & path,std::conditional_t<IS_BINARY,std::vector<char>,std::string> & out)89 bool ReadFile(const std::string &path, std::conditional_t<IS_BINARY, std::vector<char>, std::string> &out)
90 {
91 auto f = std::ifstream(path, IS_BINARY ? std::ios_base::binary : std::ios_base::in);
92 if (!f.is_open() || f.bad()) {
93 return false;
94 }
95
96 out.clear();
97
98 out.assign((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
99 return true;
100 }
101
102 // removes comments
NormalizeGold(std::string & gold)103 void NormalizeGold(std::string &gold)
104 {
105 std::string_view in = gold;
106 std::string out;
107 out.reserve(gold.size());
108 while (!in.empty()) {
109 auto nxtNl = in.find('\n');
110 if (in[0] == '#') {
111 if (nxtNl == std::string::npos) {
112 break;
113 }
114 in = in.substr(nxtNl + 1);
115 continue;
116 }
117 if (nxtNl == std::string::npos) {
118 out += in;
119 break;
120 }
121 out += in.substr(0, nxtNl + 1);
122 in = in.substr(nxtNl + 1);
123 }
124 gold = std::move(out);
125 }
126
Build(const std::string & path)127 std::optional<std::string> Build(const std::string &path)
128 {
129 std::string prog;
130
131 if (!ReadFile<false>(path + ".pa", prog)) {
132 return "can't read file " + path + ".pa";
133 }
134 ark::pandasm::Parser p;
135 auto res = p.Parse(prog, path + ".pa");
136 if (p.ShowError().err != ark::pandasm::Error::ErrorType::ERR_NONE) {
137 return p.ShowError().message + "\n" + p.ShowError().wholeLine;
138 }
139
140 if (!res.HasValue()) {
141 return "no parsed value";
142 }
143
144 auto writer = ark::panda_file::FileWriter(path + ".abc");
145 if (!ark::pandasm::AsmEmitter::Emit(&writer, res.Value())) {
146 return "can't emit";
147 }
148
149 return std::nullopt;
150 }
151
TestSingle(const std::string & path,bool isGood=true,const Config & conf=ark::static_linker::DefaultConfig (),bool * succeded=nullptr,Result * saveResult=nullptr)152 void TestSingle(const std::string &path, bool isGood = true, const Config &conf = ark::static_linker::DefaultConfig(),
153 bool *succeded = nullptr, Result *saveResult = nullptr)
154 {
155 const auto pathPrefix = "data/single/";
156 ASSERT_EQ(Build(pathPrefix + path), std::nullopt);
157 auto gold = std::string {};
158 ASSERT_TRUE(ReadFile<false>(pathPrefix + path + ".gold", gold));
159
160 NormalizeGold(gold);
161
162 const auto out = pathPrefix + path + ".linked.abc";
163 auto linkRes = Link(conf, out, {pathPrefix + path + ".abc"});
164
165 ASSERT_EQ(linkRes.errors.empty(), isGood);
166
167 if (isGood) {
168 auto res = ExecPanda(out);
169 ASSERT_EQ(res.first, 0);
170 ASSERT_EQ(res.second, gold);
171 }
172
173 if (succeded != nullptr) {
174 *succeded = true;
175 }
176
177 if (saveResult != nullptr) {
178 *saveResult = std::move(linkRes);
179 }
180 }
181
182 struct TestData {
183 std::string pathPrefix;
184 bool isGood = false;
185 Result *expected = nullptr;
186 std::string gold;
187 };
188
PerformTest(TestData * data,const std::vector<std::string> & perms,const Config & conf,std::optional<std::vector<char>> expectedFile,size_t iteration)189 void PerformTest(TestData *data, const std::vector<std::string> &perms, const Config &conf,
190 std::optional<std::vector<char>> expectedFile, size_t iteration)
191 {
192 auto out = data->pathPrefix + "linked.";
193 auto files = std::vector<std::string> {};
194
195 for (const auto &f : perms) {
196 out += f;
197 out += ".";
198 files.emplace_back(data->pathPrefix + f + ".abc");
199 }
200 out += "it";
201 out += std::to_string(iteration);
202 out += ".abc";
203
204 SCOPED_TRACE(out);
205
206 auto linkRes = Link(conf, out, files);
207 if (linkRes.errors.empty() != data->isGood) {
208 auto errs = std::string();
209 for (auto &err : linkRes.errors) {
210 errs += err;
211 errs += "\n";
212 }
213 ASSERT_EQ(linkRes.errors.empty(), data->isGood) << errs;
214 }
215
216 if (data->expected != nullptr) {
217 ASSERT_EQ(linkRes.stats.deduplicatedForeigners, data->expected->stats.deduplicatedForeigners);
218 }
219
220 if (data->isGood) {
221 std::vector<char> gotFile;
222 ASSERT_TRUE(ReadFile<true>(out, gotFile));
223 if (!expectedFile.has_value()) {
224 expectedFile = std::move(gotFile);
225 } else {
226 (void)iteration;
227 ASSERT_EQ(expectedFile.value(), gotFile) << "on iteration: " << iteration;
228 }
229
230 auto res = ExecPanda(out);
231 ASSERT_EQ(res.first, 0);
232 ASSERT_EQ(res.second, data->gold);
233 }
234 }
235
TestMultiple(const std::string & path,std::vector<std::string> perms,bool isGood=true,const Config & conf=ark::static_linker::DefaultConfig (),Result * expected=nullptr)236 void TestMultiple(const std::string &path, std::vector<std::string> perms, bool isGood = true,
237 const Config &conf = ark::static_linker::DefaultConfig(), Result *expected = nullptr)
238 {
239 std::sort(perms.begin(), perms.end());
240
241 const auto pathPrefix = "data/multi/" + path + "/";
242
243 for (const auto &p : perms) {
244 ASSERT_EQ(Build(pathPrefix + p), std::nullopt);
245 }
246
247 auto gold = std::string {};
248
249 if (isGood) {
250 ASSERT_TRUE(ReadFile<false>(pathPrefix + "out.gold", gold));
251 NormalizeGold(gold);
252 }
253
254 std::optional<std::vector<char>> expectedFile;
255
256 do {
257 expectedFile = std::nullopt;
258 TestData data;
259 data.pathPrefix = pathPrefix;
260 data.isGood = isGood;
261 data.expected = expected;
262 data.gold = gold;
263 for (size_t iteration = 0; iteration < TEST_REPEAT_COUNT; iteration++) {
264 PerformTest(&data, perms, conf, expectedFile, iteration);
265 }
266 } while (std::next_permutation(perms.begin(), perms.end()));
267 }
268
TEST(linkertests,HelloWorld)269 TEST(linkertests, HelloWorld)
270 {
271 TestSingle("hello_world");
272 }
273
TEST(linkertests,LitArray)274 TEST(linkertests, LitArray)
275 {
276 TestSingle("lit_array");
277 }
278
TEST(linkertests,Exceptions)279 TEST(linkertests, Exceptions)
280 {
281 TestSingle("exceptions");
282 }
283
TEST(linkertests,ForeignMethod)284 TEST(linkertests, ForeignMethod)
285 {
286 TestMultiple("fmethod", {"1", "2"});
287 }
288
TEST(linkertests,ForeignField)289 TEST(linkertests, ForeignField)
290 {
291 TestMultiple("ffield", {"1", "2"});
292 }
293
TEST(linkertests,BadForeignField)294 TEST(linkertests, BadForeignField)
295 {
296 TestMultiple("bad_ffield", {"1", "2"}, false);
297 }
298
TEST(linkertests,BadClassRedefinition)299 TEST(linkertests, BadClassRedefinition)
300 {
301 TestMultiple("bad_class_redefinition", {"1", "2"}, false);
302 }
303
TEST(linkertests,BadFFieldType)304 TEST(linkertests, BadFFieldType)
305 {
306 TestMultiple("bad_ffield_type", {"1", "2"}, false);
307 }
308
TEST(linkertests,FMethodOverloaded)309 TEST(linkertests, FMethodOverloaded)
310 {
311 TestMultiple("fmethod_overloaded", {"1", "2"});
312 }
313
TEST(linkertests,FMethodOverloaded2)314 TEST(linkertests, FMethodOverloaded2)
315 {
316 TestMultiple("fmethod_overloaded_2", {"1", "2", "3", "4"});
317 }
318
TEST(linkertests,BadFMethodOverloaded)319 TEST(linkertests, BadFMethodOverloaded)
320 {
321 TestMultiple("bad_fmethod_overloaded", {"1", "2"}, false);
322 }
323
TEST(linkertests,DeduplicatedField)324 TEST(linkertests, DeduplicatedField)
325 {
326 auto res = Result {};
327 res.stats.deduplicatedForeigners = 1;
328 TestMultiple("dedup_field", {"1", "2"}, true, DefaultConfig(), &res);
329 }
330
TEST(linkertests,DeduplicatedMethod)331 TEST(linkertests, DeduplicatedMethod)
332 {
333 auto res = Result {};
334 res.stats.deduplicatedForeigners = 1;
335 TestMultiple("dedup_method", {"1", "2"}, true, DefaultConfig(), &res);
336 }
337
TEST(linkertests,UnresolvedInGlobal)338 TEST(linkertests, UnresolvedInGlobal)
339 {
340 TestSingle("unresolved_global", false);
341 auto conf = DefaultConfig();
342 conf.remainsPartial = {std::string(ark::panda_file::ItemContainer::GetGlobalClassName())};
343 TestSingle("unresolved_global", true, conf);
344 }
345
TEST(linkertests,DeduplicateLineNumberNrogram)346 TEST(linkertests, DeduplicateLineNumberNrogram)
347 {
348 auto succ = false;
349 auto res = Result {};
350 TestSingle("lnp_dedup", true, DefaultConfig(), &succ, &res);
351 ASSERT_TRUE(succ);
352 ASSERT_EQ(res.stats.debugCount, 1);
353 }
354
TEST(linkertests,StripDebugInfo)355 TEST(linkertests, StripDebugInfo)
356 {
357 auto succ = false;
358 auto res = Result {};
359 auto conf = DefaultConfig();
360 conf.stripDebugInfo = true;
361 TestSingle("hello_world", true, conf, &succ, &res);
362 ASSERT_TRUE(succ);
363 ASSERT_EQ(res.stats.debugCount, 0);
364 }
365
TEST(linkertests,FieldOverload)366 TEST(linkertests, FieldOverload)
367 {
368 auto conf = DefaultConfig();
369 conf.partial.emplace("LFor;");
370 TestMultiple("ffield_overloaded", {"1", "2"}, true, conf);
371 }
372
TEST(linkertests,ForeignBase)373 TEST(linkertests, ForeignBase)
374 {
375 #ifdef PANDA_WITH_ETS
376 constexpr auto LANG = ark::panda_file::SourceLang::ETS;
377 auto makeRecord = [](ark::pandasm::Program &prog, const std::string &name) {
378 return &prog.recordTable.emplace(name, ark::pandasm::Record(name, LANG)).first->second;
379 };
380
381 const std::string basePath = "data/multi/ForeignBase.1.abc";
382 const std::string dervPath = "data/multi/ForeignBase.2.abc";
383
384 {
385 ark::pandasm::Program progBase;
386 auto base = makeRecord(progBase, "Base");
387 auto fld = ark::pandasm::Field(LANG);
388 fld.name = "fld";
389 fld.type = ark::pandasm::Type("i32", 0);
390 base->fieldList.push_back(std::move(fld));
391
392 ASSERT_TRUE(ark::pandasm::AsmEmitter::Emit(basePath, progBase));
393 }
394
395 {
396 ark::pandasm::Program progDer;
397 auto base = makeRecord(progDer, "Base");
398 base->metadata->SetAttribute("external");
399
400 auto derv = makeRecord(progDer, "Derv");
401 ASSERT_EQ(derv->metadata->SetAttributeValue("ets.extends", "Base"), std::nullopt);
402 std::ignore = derv;
403 auto fld = ark::pandasm::Field(LANG);
404 fld.name = "fld";
405 fld.type = ark::pandasm::Type("i32", 0);
406 fld.metadata->SetAttribute("external");
407 derv->fieldList.push_back(std::move(fld));
408
409 auto func = ark::pandasm::Function("main", LANG);
410 func.regsNum = 1;
411 func.returnType = ark::pandasm::Type("void", 0);
412 func.AddInstruction(ark::pandasm::Create_NEWOBJ(0, "Derv"));
413 func.AddInstruction(ark::pandasm::Create_LDOBJ(0, "Derv.fld"));
414 func.AddInstruction(ark::pandasm::Create_RETURN_VOID());
415
416 progDer.functionTable.emplace(func.name, std::move(func));
417
418 ASSERT_TRUE(ark::pandasm::AsmEmitter::Emit(dervPath, progDer));
419 }
420
421 auto res = Link(DefaultConfig(), "data/multi/ForeignBase.linked.abc", {basePath, dervPath});
422 ASSERT_TRUE(res.errors.empty()) << res.errors.front();
423 #endif
424 }
425 } // namespace
426