• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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