1 /**
2 * Copyright (c) 2021-2022 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 = panda::static_linker::Config;
44 using Result = panda::static_linker::Result;
45 using panda::static_linker::DefaultConfig;
46 using panda::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 = panda::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 (!panda::Runtime::Create(opts)) {
63 return {1, "can't create runtime"};
64 }
65
66 auto *runtime = panda::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 (!panda::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 panda::pandasm::Parser p;
135 auto res = p.Parse(prog, path + ".pa");
136 if (p.ShowError().err != panda::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 = panda::panda_file::FileWriter(path + ".abc");
145 if (!panda::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=panda::static_linker::DefaultConfig (),bool * succeded=nullptr,Result * saveResult=nullptr)152 void TestSingle(const std::string &path, bool isGood = true, const Config &conf = panda::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
TestMultiple(const std::string & path,std::vector<std::string> perms,bool isGood=true,const Config & conf=panda::static_linker::DefaultConfig (),Result * expected=nullptr)182 void TestMultiple(const std::string &path, std::vector<std::string> perms, bool isGood = true,
183 const Config &conf = panda::static_linker::DefaultConfig(), Result *expected = nullptr)
184 {
185 std::sort(perms.begin(), perms.end());
186
187 const auto pathPrefix = "data/multi/" + path + "/";
188
189 for (const auto &p : perms) {
190 ASSERT_EQ(Build(pathPrefix + p), std::nullopt);
191 }
192
193 auto gold = std::string {};
194
195 if (isGood) {
196 ASSERT_TRUE(ReadFile<false>(pathPrefix + "out.gold", gold));
197 NormalizeGold(gold);
198 }
199
200 auto out = std::string {};
201 auto files = std::vector<std::string> {};
202
203 std::optional<std::vector<char>> expectedFile;
204
205 auto performTest = [&out, &pathPrefix, &files, &perms, &conf, &isGood, &expected, &expectedFile,
206 &gold](size_t iteration) {
207 out = pathPrefix + "linked.";
208 files.clear();
209
210 for (const auto &f : perms) {
211 out += f;
212 out += ".";
213 files.emplace_back(pathPrefix + f + ".abc");
214 }
215 out += "it";
216 out += std::to_string(iteration);
217 out += ".abc";
218
219 SCOPED_TRACE(out);
220
221 auto linkRes = Link(conf, out, files);
222 if (linkRes.errors.empty() != isGood) {
223 auto errs = std::string();
224 for (auto &err : linkRes.errors) {
225 errs += err;
226 errs += "\n";
227 }
228 ASSERT_EQ(linkRes.errors.empty(), isGood) << errs;
229 }
230
231 if (expected != nullptr) {
232 ASSERT_EQ(linkRes.stats.deduplicatedForeigners, expected->stats.deduplicatedForeigners);
233 }
234
235 if (isGood) {
236 std::vector<char> gotFile;
237 ASSERT_TRUE(ReadFile<true>(out, gotFile));
238 if (!expectedFile.has_value()) {
239 expectedFile = std::move(gotFile);
240 } else {
241 (void)iteration;
242 ASSERT_EQ(expectedFile.value(), gotFile) << "on iteration: " << iteration;
243 }
244
245 auto res = ExecPanda(out);
246 ASSERT_EQ(res.first, 0);
247 ASSERT_EQ(res.second, gold);
248 }
249 };
250
251 do {
252 expectedFile = std::nullopt;
253 for (size_t iteration = 0; iteration < TEST_REPEAT_COUNT; iteration++) {
254 performTest(iteration);
255 }
256 } while (std::next_permutation(perms.begin(), perms.end()));
257 }
258 } // namespace
259
TEST(linkertests,HelloWorld)260 TEST(linkertests, HelloWorld)
261 {
262 TestSingle("hello_world");
263 }
264
TEST(linkertests,LitArray)265 TEST(linkertests, LitArray)
266 {
267 TestSingle("lit_array");
268 }
269
TEST(linkertests,Exceptions)270 TEST(linkertests, Exceptions)
271 {
272 TestSingle("exceptions");
273 }
274
TEST(linkertests,ForeignMethod)275 TEST(linkertests, ForeignMethod)
276 {
277 TestMultiple("fmethod", {"1", "2"});
278 }
279
TEST(linkertests,ForeignField)280 TEST(linkertests, ForeignField)
281 {
282 TestMultiple("ffield", {"1", "2"});
283 }
284
TEST(linkertests,BadForeignField)285 TEST(linkertests, BadForeignField)
286 {
287 TestMultiple("bad_ffield", {"1", "2"}, false);
288 }
289
TEST(linkertests,BadClassRedefinition)290 TEST(linkertests, BadClassRedefinition)
291 {
292 TestMultiple("bad_class_redefinition", {"1", "2"}, false);
293 }
294
TEST(linkertests,BadFFieldType)295 TEST(linkertests, BadFFieldType)
296 {
297 TestMultiple("bad_ffield_type", {"1", "2"}, false);
298 }
299
TEST(linkertests,FMethodOverloaded)300 TEST(linkertests, FMethodOverloaded)
301 {
302 TestMultiple("fmethod_overloaded", {"1", "2"});
303 }
304
TEST(linkertests,FMethodOverloaded2)305 TEST(linkertests, FMethodOverloaded2)
306 {
307 TestMultiple("fmethod_overloaded_2", {"1", "2", "3", "4"});
308 }
309
TEST(linkertests,BadFMethodOverloaded)310 TEST(linkertests, BadFMethodOverloaded)
311 {
312 TestMultiple("bad_fmethod_overloaded", {"1", "2"}, false);
313 }
314
TEST(linkertests,DeduplicatedField)315 TEST(linkertests, DeduplicatedField)
316 {
317 auto res = Result {};
318 res.stats.deduplicatedForeigners = 1;
319 TestMultiple("dedup_field", {"1", "2"}, true, DefaultConfig(), &res);
320 }
321
TEST(linkertests,DeduplicatedMethod)322 TEST(linkertests, DeduplicatedMethod)
323 {
324 auto res = Result {};
325 res.stats.deduplicatedForeigners = 1;
326 TestMultiple("dedup_method", {"1", "2"}, true, DefaultConfig(), &res);
327 }
328
TEST(linkertests,UnresolvedInGlobal)329 TEST(linkertests, UnresolvedInGlobal)
330 {
331 TestSingle("unresolved_global", false);
332 auto conf = DefaultConfig();
333 conf.remainsPartial = {std::string(panda::panda_file::ItemContainer::GetGlobalClassName())};
334 TestSingle("unresolved_global", true, conf);
335 }
336
TEST(linkertests,DeduplicateLineNumberNrogram)337 TEST(linkertests, DeduplicateLineNumberNrogram)
338 {
339 auto succ = false;
340 auto res = Result {};
341 TestSingle("lnp_dedup", true, DefaultConfig(), &succ, &res);
342 ASSERT_TRUE(succ);
343 ASSERT_EQ(res.stats.debugCount, 1);
344 }
345
TEST(linkertests,StripDebugInfo)346 TEST(linkertests, StripDebugInfo)
347 {
348 auto succ = false;
349 auto res = Result {};
350 auto conf = DefaultConfig();
351 conf.stripDebugInfo = true;
352 TestSingle("hello_world", true, conf, &succ, &res);
353 ASSERT_TRUE(succ);
354 ASSERT_EQ(res.stats.debugCount, 0);
355 }
356
TEST(linkertests,FieldOverload)357 TEST(linkertests, FieldOverload)
358 {
359 auto conf = DefaultConfig();
360 conf.partial.emplace("LFor;");
361 TestMultiple("ffield_overloaded", {"1", "2"}, true, conf);
362 }
363
TEST(linkertests,ForeignBase)364 TEST(linkertests, ForeignBase)
365 {
366 #ifdef PANDA_WITH_ETS
367 constexpr auto LANG = panda::panda_file::SourceLang::ETS;
368 auto makeRecord = [](panda::pandasm::Program &prog, const std::string &name) {
369 return &prog.recordTable.emplace(name, panda::pandasm::Record(name, LANG)).first->second;
370 };
371
372 const std::string basePath = "data/multi/ForeignBase.1.abc";
373 const std::string dervPath = "data/multi/ForeignBase.2.abc";
374
375 {
376 panda::pandasm::Program progBase;
377 auto base = makeRecord(progBase, "Base");
378 auto fld = panda::pandasm::Field(LANG);
379 fld.name = "fld";
380 fld.type = panda::pandasm::Type("i32", 0);
381 base->fieldList.push_back(std::move(fld));
382
383 ASSERT_TRUE(panda::pandasm::AsmEmitter::Emit(basePath, progBase));
384 }
385
386 {
387 panda::pandasm::Program progDer;
388 auto base = makeRecord(progDer, "Base");
389 base->metadata->SetAttribute("external");
390
391 auto derv = makeRecord(progDer, "Derv");
392 ASSERT_EQ(derv->metadata->SetAttributeValue("ets.extends", "Base"), std::nullopt);
393 std::ignore = derv;
394 auto fld = panda::pandasm::Field(LANG);
395 fld.name = "fld";
396 fld.type = panda::pandasm::Type("i32", 0);
397 fld.metadata->SetAttribute("external");
398 derv->fieldList.push_back(std::move(fld));
399
400 auto func = panda::pandasm::Function("main", LANG);
401 func.regsNum = 1;
402 func.returnType = panda::pandasm::Type("void", 0);
403 func.AddInstruction(panda::pandasm::Create_NEWOBJ(0, "Derv"));
404 func.AddInstruction(panda::pandasm::Create_LDOBJ(0, "Derv.fld"));
405 func.AddInstruction(panda::pandasm::Create_RETURN_VOID());
406
407 progDer.functionTable.emplace(func.name, std::move(func));
408
409 ASSERT_TRUE(panda::pandasm::AsmEmitter::Emit(dervPath, progDer));
410 }
411
412 auto res = Link(DefaultConfig(), "data/multi/ForeignBase.linked.abc", {basePath, dervPath});
413 ASSERT_TRUE(res.errors.empty()) << res.errors.front();
414 #endif
415 }
416