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