• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "sysprop_cpp_gen"
18 
19 #include "CppGen.h"
20 
21 #include <android-base/file.h>
22 #include <android-base/logging.h>
23 #include <android-base/stringprintf.h>
24 #include <android-base/strings.h>
25 #include <cerrno>
26 #include <filesystem>
27 #include <regex>
28 #include <string>
29 
30 #include "CodeWriter.h"
31 #include "Common.h"
32 #include "sysprop.pb.h"
33 
34 using android::base::Result;
35 
36 namespace {
37 
38 constexpr const char* kIndent = "    ";
39 
40 constexpr const char* kCppHeaderIncludes =
41     R"(#include <cstdint>
42 #include <optional>
43 #include <string>
44 #include <vector>
45 
46 )";
47 
48 constexpr const char* kCppSourceIncludes =
49     R"(#include <cctype>
50 #include <cerrno>
51 #include <cstdio>
52 #include <cstring>
53 #include <limits>
54 #include <utility>
55 
56 #include <strings.h>
57 #include <sys/system_properties.h>
58 
59 #include <android-base/parseint.h>
60 #include <log/log.h>
61 
62 )";
63 
64 constexpr const char* kCppParsersAndFormatters =
65     R"(template <typename T> constexpr bool is_vector = false;
66 
67 template <typename T> constexpr bool is_vector<std::vector<T>> = true;
68 
69 template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) {
70     static constexpr const char* kYes[] = {"1", "true"};
71     static constexpr const char* kNo[] = {"0", "false"};
72 
73     for (const char* yes : kYes) {
74         if (strcasecmp(yes, str) == 0) return std::make_optional(true);
75     }
76 
77     for (const char* no : kNo) {
78         if (strcasecmp(no, str) == 0) return std::make_optional(false);
79     }
80 
81     return std::nullopt;
82 }
83 
84 template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) {
85     std::int32_t ret;
86     return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
87 }
88 
89 template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) {
90     std::int64_t ret;
91     return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
92 }
93 
94 template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) {
95     int old_errno = errno;
96     errno = 0;
97     char* end;
98     double ret = std::strtod(str, &end);
99     if (errno != 0) {
100         return std::nullopt;
101     }
102     if (str == end || *end != '\0') {
103         errno = EINVAL;
104         return std::nullopt;
105     }
106     errno = old_errno;
107     return std::make_optional(ret);
108 }
109 
110 template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) {
111     return *str == '\0' ? std::nullopt : std::make_optional(str);
112 }
113 
114 template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) {
115     Vec ret;
116     if (*str == '\0') return ret;
117     const char* p = str;
118     for (;;) {
119         const char* r = p;
120         std::string value;
121         while (*r != ',') {
122             if (*r == '\\') ++r;
123             if (*r == '\0') break;
124             value += *r++;
125         }
126         ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str()));
127         if (*r == '\0') break;
128         p = r + 1;
129     }
130     return ret;
131 }
132 
133 template <typename T> inline T TryParse(const char* str) {
134     if constexpr(is_vector<T>) {
135         return DoParseList<T>(str);
136     } else {
137         return DoParse<T>(str);
138     }
139 }
140 
141 [[maybe_unused]] std::string FormatValue(const std::optional<std::int32_t>& value) {
142     return value ? std::to_string(*value) : "";
143 }
144 
145 [[maybe_unused]] std::string FormatValue(const std::optional<std::int64_t>& value) {
146     return value ? std::to_string(*value) : "";
147 }
148 
149 [[maybe_unused]] std::string FormatValue(const std::optional<double>& value) {
150     if (!value) return "";
151     char buf[1024];
152     std::sprintf(buf, "%.*g", std::numeric_limits<double>::max_digits10, *value);
153     return buf;
154 }
155 
156 [[maybe_unused]] std::string FormatValue(const std::optional<bool>& value) {
157     return value ? (*value ? "true" : "false") : "";
158 }
159 
160 template <typename T>
161 [[maybe_unused]] std::string FormatValue(const std::vector<T>& value) {
162     if (value.empty()) return "";
163 
164     std::string ret;
165     bool first = true;
166 
167     for (auto&& element : value) {
168         if (!first) ret += ',';
169         else first = false;
170         if constexpr(std::is_same_v<T, std::optional<std::string>>) {
171             if (element) {
172                 for (char c : *element) {
173                     if (c == '\\' || c == ',') ret += '\\';
174                     ret += c;
175                 }
176             }
177         } else {
178             ret += FormatValue(element);
179         }
180     }
181 
182     return ret;
183 }
184 
185 template <typename T>
186 T GetProp(const char* key) {
187     T ret;
188     auto pi = __system_property_find(key);
189     if (pi != nullptr) {
190         __system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) {
191             *static_cast<T*>(cookie) = TryParse<T>(value);
192         }, &ret);
193     }
194     return ret;
195 }
196 
197 )";
198 
199 const std::regex kRegexDot{"\\."};
200 const std::regex kRegexUnderscore{"_"};
201 
202 std::string GetCppEnumName(const sysprop::Property& prop);
203 std::string GetCppPropTypeName(const sysprop::Property& prop);
204 std::string GetCppNamespace(const sysprop::Properties& props);
205 
206 std::string GenerateHeader(const sysprop::Properties& props,
207                            sysprop::Scope scope);
208 std::string GenerateSource(const sysprop::Properties& props,
209                            const std::string& include_name);
210 
GetCppEnumName(const sysprop::Property & prop)211 std::string GetCppEnumName(const sysprop::Property& prop) {
212   return ApiNameToIdentifier(prop.api_name()) + "_values";
213 }
214 
GetCppPropTypeName(const sysprop::Property & prop)215 std::string GetCppPropTypeName(const sysprop::Property& prop) {
216   switch (prop.type()) {
217     case sysprop::Boolean:
218       return "std::optional<bool>";
219     case sysprop::Integer:
220       return "std::optional<std::int32_t>";
221     case sysprop::Long:
222       return "std::optional<std::int64_t>";
223     case sysprop::Double:
224       return "std::optional<double>";
225     case sysprop::String:
226       return "std::optional<std::string>";
227     case sysprop::Enum:
228       return "std::optional<" + GetCppEnumName(prop) + ">";
229     case sysprop::BooleanList:
230       return "std::vector<std::optional<bool>>";
231     case sysprop::IntegerList:
232       return "std::vector<std::optional<std::int32_t>>";
233     case sysprop::LongList:
234       return "std::vector<std::optional<std::int64_t>>";
235     case sysprop::DoubleList:
236       return "std::vector<std::optional<double>>";
237     case sysprop::StringList:
238       return "std::vector<std::optional<std::string>>";
239     case sysprop::EnumList:
240       return "std::vector<std::optional<" + GetCppEnumName(prop) + ">>";
241     default:
242       __builtin_unreachable();
243   }
244 }
245 
GetCppNamespace(const sysprop::Properties & props)246 std::string GetCppNamespace(const sysprop::Properties& props) {
247   return std::regex_replace(props.module(), kRegexDot, "::");
248 }
249 
GenerateHeader(const sysprop::Properties & props,sysprop::Scope scope)250 std::string GenerateHeader(const sysprop::Properties& props,
251                            sysprop::Scope scope) {
252   CodeWriter writer(kIndent);
253 
254   writer.Write("%s", kGeneratedFileFooterComments);
255 
256   writer.Write("#pragma once\n\n");
257   writer.Write("%s", kCppHeaderIncludes);
258 
259   std::string cpp_namespace = GetCppNamespace(props);
260   writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
261 
262   bool first = true;
263 
264   for (int i = 0; i < props.prop_size(); ++i) {
265     const sysprop::Property& prop = props.prop(i);
266 
267     // Scope: Internal > Public
268     if (prop.scope() > scope) continue;
269 
270     if (!first) {
271       writer.Write("\n");
272     } else {
273       first = false;
274     }
275 
276     std::string prop_id = ApiNameToIdentifier(prop.api_name());
277     std::string prop_type = GetCppPropTypeName(prop);
278 
279     if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
280       writer.Write("enum class %s {\n", GetCppEnumName(prop).c_str());
281       writer.Indent();
282       for (const std::string& name :
283            android::base::Split(prop.enum_values(), "|")) {
284         writer.Write("%s,\n", ToUpper(name).c_str());
285       }
286       writer.Dedent();
287       writer.Write("};\n\n");
288     }
289 
290     if (prop.deprecated()) writer.Write("[[deprecated]] ");
291     writer.Write("%s %s();\n", prop_type.c_str(), prop_id.c_str());
292     if (prop.access() != sysprop::Readonly) {
293       if (prop.deprecated()) writer.Write("[[deprecated]] ");
294       writer.Write("bool %s(const %s& value);\n", prop_id.c_str(),
295                    prop_type.c_str());
296     }
297   }
298 
299   writer.Write("\n}  // namespace %s\n", cpp_namespace.c_str());
300 
301   return writer.Code();
302 }
303 
GenerateSource(const sysprop::Properties & props,const std::string & include_name)304 std::string GenerateSource(const sysprop::Properties& props,
305                            const std::string& include_name) {
306   CodeWriter writer(kIndent);
307   writer.Write("%s", kGeneratedFileFooterComments);
308   writer.Write("#include <%s>\n\n", include_name.c_str());
309   writer.Write("%s", kCppSourceIncludes);
310 
311   std::string cpp_namespace = GetCppNamespace(props);
312 
313   writer.Write("namespace {\n\n");
314   writer.Write("using namespace %s;\n\n", cpp_namespace.c_str());
315   writer.Write("template <typename T> T DoParse(const char* str);\n\n");
316 
317   for (int i = 0; i < props.prop_size(); ++i) {
318     const sysprop::Property& prop = props.prop(i);
319     if (prop.type() != sysprop::Enum && prop.type() != sysprop::EnumList) {
320       continue;
321     }
322 
323     std::string prop_id = ApiNameToIdentifier(prop.api_name());
324     std::string enum_name = GetCppEnumName(prop);
325 
326     writer.Write("constexpr const std::pair<const char*, %s> %s_list[] = {\n",
327                  enum_name.c_str(), prop_id.c_str());
328     writer.Indent();
329     for (const std::string& name :
330          android::base::Split(prop.enum_values(), "|")) {
331       writer.Write("{\"%s\", %s::%s},\n", name.c_str(), enum_name.c_str(),
332                    ToUpper(name).c_str());
333     }
334     writer.Dedent();
335     writer.Write("};\n\n");
336 
337     writer.Write("template <>\n");
338     writer.Write("std::optional<%s> DoParse(const char* str) {\n",
339                  enum_name.c_str());
340     writer.Indent();
341     writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
342     writer.Indent();
343     writer.Write("if (strcmp(str, name) == 0) {\n");
344     writer.Indent();
345     writer.Write("return val;\n");
346     writer.Dedent();
347     writer.Write("}\n");
348     writer.Dedent();
349     writer.Write("}\n");
350     writer.Write("return std::nullopt;\n");
351     writer.Dedent();
352     writer.Write("}\n\n");
353 
354     if (prop.access() != sysprop::Readonly) {
355       writer.Write("std::string FormatValue(std::optional<%s> value) {\n",
356                    enum_name.c_str());
357       writer.Indent();
358       writer.Write("if (!value) return \"\";\n");
359       writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
360       writer.Indent();
361       writer.Write("if (val == *value) {\n");
362       writer.Indent();
363       writer.Write("return name;\n");
364       writer.Dedent();
365       writer.Write("}\n");
366       writer.Dedent();
367       writer.Write("}\n");
368 
369       writer.Write(
370           "LOG_ALWAYS_FATAL(\"Invalid value %%d for property %s\", "
371           "static_cast<std::int32_t>(*value));\n",
372           prop.prop_name().c_str());
373 
374       writer.Write("__builtin_unreachable();\n");
375       writer.Dedent();
376       writer.Write("}\n\n");
377     }
378   }
379   writer.Write("%s", kCppParsersAndFormatters);
380   writer.Write("}  // namespace\n\n");
381 
382   writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
383 
384   for (int i = 0; i < props.prop_size(); ++i) {
385     if (i > 0) writer.Write("\n");
386 
387     const sysprop::Property& prop = props.prop(i);
388     std::string prop_id = ApiNameToIdentifier(prop.api_name());
389     std::string prop_type = GetCppPropTypeName(prop);
390 
391     writer.Write("%s %s() {\n", prop_type.c_str(), prop_id.c_str());
392     writer.Indent();
393     writer.Write("return GetProp<%s>(\"%s\");\n", prop_type.c_str(),
394                  prop.prop_name().c_str());
395     writer.Dedent();
396     writer.Write("}\n");
397 
398     if (prop.access() != sysprop::Readonly) {
399       writer.Write("\nbool %s(const %s& value) {\n", prop_id.c_str(),
400                    prop_type.c_str());
401       writer.Indent();
402 
403       const char* format_expr = "FormatValue(value).c_str()";
404 
405       // Specialized formatters here
406       if (prop.type() == sysprop::String) {
407         format_expr = "value ? value->c_str() : \"\"";
408       } else if (prop.integer_as_bool()) {
409         if (prop.type() == sysprop::Boolean) {
410           // optional<bool> -> optional<int>
411           format_expr = "FormatValue(std::optional<int>(value)).c_str()";
412         } else if (prop.type() == sysprop::BooleanList) {
413           // vector<optional<bool>> -> vector<optional<int>>
414           format_expr =
415               "FormatValue(std::vector<std::optional<int>>("
416               "value.begin(), value.end())).c_str()";
417         }
418       }
419 
420       writer.Write("return __system_property_set(\"%s\", %s) == 0;\n",
421                    prop.prop_name().c_str(), format_expr);
422       writer.Dedent();
423       writer.Write("}\n");
424     }
425   }
426 
427   writer.Write("\n}  // namespace %s\n", cpp_namespace.c_str());
428 
429   return writer.Code();
430 }
431 
432 }  // namespace
433 
GenerateCppFiles(const std::string & input_file_path,const std::string & header_dir,const std::string & public_header_dir,const std::string & source_output_dir,const std::string & include_name)434 Result<void> GenerateCppFiles(const std::string& input_file_path,
435                               const std::string& header_dir,
436                               const std::string& public_header_dir,
437                               const std::string& source_output_dir,
438                               const std::string& include_name) {
439   sysprop::Properties props;
440 
441   if (auto res = ParseProps(input_file_path); res.ok()) {
442     props = std::move(*res);
443   } else {
444     return res.error();
445   }
446 
447   std::string output_basename = android::base::Basename(input_file_path);
448 
449   for (auto&& [scope, dir] : {
450            std::pair(sysprop::Internal, header_dir),
451            std::pair(sysprop::Public, public_header_dir),
452        }) {
453     std::error_code ec;
454     std::filesystem::create_directories(dir, ec);
455     if (ec) {
456       return Errorf("Creating directory to {} failed: {}", dir, ec.message());
457     }
458 
459     std::string path = dir + "/" + output_basename + ".h";
460     std::string result = GenerateHeader(props, scope);
461 
462     if (!android::base::WriteStringToFile(result, path)) {
463       return ErrnoErrorf("Writing generated header to {} failed", path);
464     }
465   }
466 
467   std::string source_path = source_output_dir + "/" + output_basename + ".cpp";
468   std::string source_result = GenerateSource(props, include_name);
469 
470   if (!android::base::WriteStringToFile(source_result, source_path)) {
471     return ErrnoErrorf("Writing generated source to {} failed", source_path);
472   }
473 
474   return {};
475 }
476