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