Add a Rust backend.
Fixes: 178390804
Test: Build
Test: Call one generated function from Rust without crashing.
Test: Use a pull API and verify the callback logs atoms.
Test: Manually compare some functions to their C++ counterparts.
Change-Id: I4017b9e18951d7c32da3dcf5a7fae4394f9313ea
diff --git a/stats/stats_log_api_gen/Android.bp b/stats/stats_log_api_gen/Android.bp
index 11fe7ac..220f84e 100644
--- a/stats/stats_log_api_gen/Android.bp
+++ b/stats/stats_log_api_gen/Android.bp
@@ -29,6 +29,7 @@
"java_writer_q.cpp",
"main.cpp",
"native_writer.cpp",
+ "rust_writer.cpp",
"utils.cpp",
],
cflags: [
@@ -153,3 +154,39 @@
},
},
}
+
+// ==========================================================
+// Rust library
+// ==========================================================
+
+genrule {
+ name: "statslog.rs",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --rust $(genDir)/statslog.rs",
+ out: [
+ "statslog.rs",
+ ],
+}
+
+rust_library {
+ name: "libstatslog_rust",
+ crate_name: "statslog_rust",
+ srcs: [
+ "rust_wrapper.rs",
+ ":statslog.rs",
+ ],
+ rustlibs: [
+ "libnum_traits",
+ "libstatspull_bindgen",
+ "libthiserror",
+ ],
+ proc_macros: ["libnum_derive"],
+ target: {
+ android: {
+ shared_libs: ["libstatspull"],
+ },
+ host: {
+ static_libs: ["libstatspull"],
+ },
+ },
+}
diff --git a/stats/stats_log_api_gen/Collation.cpp b/stats/stats_log_api_gen/Collation.cpp
index 56f4db0..0b5e206 100644
--- a/stats/stats_log_api_gen/Collation.cpp
+++ b/stats/stats_log_api_gen/Collation.cpp
@@ -47,6 +47,7 @@
name(that.name),
message(that.message),
fields(that.fields),
+ oneOfName(that.oneOfName),
fieldNumberToAnnotations(that.fieldNumberToAnnotations),
primaryFields(that.primaryFields),
exclusiveField(that.exclusiveField),
@@ -56,7 +57,8 @@
uidField(that.uidField) {
}
-AtomDecl::AtomDecl(int c, const string& n, const string& m) : code(c), name(n), message(m) {
+AtomDecl::AtomDecl(int c, const string& n, const string& m, const string &o)
+ : code(c), name(n), message(m), oneOfName(o) {
}
AtomDecl::~AtomDecl() {
@@ -512,9 +514,18 @@
continue;
}
+ const OneofDescriptor* oneofAtom = atomField->containing_oneof();
+ if (oneofAtom == nullptr) {
+ print_error(atomField, "Atom is not declared in a `oneof` field: %s\n",
+ atomField->name().c_str());
+ errorCount++;
+ continue;
+ }
+
const Descriptor* atom = atomField->message_type();
shared_ptr<AtomDecl> atomDecl =
- make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name());
+ make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name(),
+ oneofAtom->name());
if (atomField->options().GetExtension(os::statsd::truncate_timestamp)) {
addAnnotationToAtomDecl(atomDecl.get(), ATOM_ID_FIELD_NUMBER,
@@ -534,13 +545,7 @@
continue;
}
- const OneofDescriptor* oneofAtom = atomField->containing_oneof();
- if (oneofAtom == nullptr) {
- print_error(atomField, "Atom is not declared in a `oneof` field: %s\n",
- atomField->name().c_str());
- errorCount++;
- continue;
- } else if ((oneofAtom->name() != ONEOF_PUSHED_ATOM_NAME) &&
+ if ((oneofAtom->name() != ONEOF_PUSHED_ATOM_NAME) &&
(oneofAtom->name() != ONEOF_PULLED_ATOM_NAME)) {
print_error(atomField, "Atom is neither a pushed nor pulled atom: %s\n",
atomField->name().c_str());
@@ -556,7 +561,8 @@
atoms->decls.insert(atomDecl);
shared_ptr<AtomDecl> nonChainedAtomDecl =
- make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name());
+ make_shared<AtomDecl>(atomField->number(), atomField->name(), atom->name(),
+ oneofAtom->name());
vector<java_type_t> nonChainedSignature;
if (get_non_chained_node(atom, nonChainedAtomDecl.get(), &nonChainedSignature)) {
FieldNumberToAtomDeclSet& nonChainedFieldNumberToAtomDeclSet =
diff --git a/stats/stats_log_api_gen/Collation.h b/stats/stats_log_api_gen/Collation.h
index b13851c..b78b556 100644
--- a/stats/stats_log_api_gen/Collation.h
+++ b/stats/stats_log_api_gen/Collation.h
@@ -163,6 +163,8 @@
string message;
vector<AtomField> fields;
+ string oneOfName;
+
FieldNumberToAnnotations fieldNumberToAnnotations;
vector<int> primaryFields;
@@ -175,7 +177,7 @@
AtomDecl();
AtomDecl(const AtomDecl& that);
- AtomDecl(int code, const string& name, const string& message);
+ AtomDecl(int code, const string& name, const string& message, const string& oneOfName);
~AtomDecl();
inline bool operator<(const AtomDecl& that) const {
diff --git a/stats/stats_log_api_gen/main.cpp b/stats/stats_log_api_gen/main.cpp
index 6b77f62..0b8cd57 100644
--- a/stats/stats_log_api_gen/main.cpp
+++ b/stats/stats_log_api_gen/main.cpp
@@ -13,6 +13,7 @@
#include "java_writer.h"
#include "java_writer_q.h"
#include "native_writer.h"
+#include "rust_writer.h"
#include "utils.h"
namespace android {
@@ -28,6 +29,7 @@
fprintf(stderr, " --header FILENAME the cpp file to output for write helpers\n");
fprintf(stderr, " --help this message\n");
fprintf(stderr, " --java FILENAME the java file to output\n");
+ fprintf(stderr, " --rust FILENAME the rust file to output\n");
fprintf(stderr, " --module NAME optional, module name to generate outputs for\n");
fprintf(stderr,
" --namespace COMMA,SEP,NAMESPACE required for cpp/header with "
@@ -63,6 +65,7 @@
string javaFilename;
string javaPackage;
string javaClass;
+ string rustFilename;
string moduleName = DEFAULT_MODULE_NAME;
string cppNamespace = DEFAULT_CPP_NAMESPACE;
@@ -97,6 +100,13 @@
return 1;
}
javaFilename = argv[index];
+ } else if (0 == strcmp("--rust", argv[index])) {
+ index++;
+ if (index >= argc) {
+ print_usage();
+ return 1;
+ }
+ rustFilename = argv[index];
} else if (0 == strcmp("--module", argv[index])) {
index++;
if (index >= argc) {
@@ -159,7 +169,8 @@
index++;
}
- if (cppFilename.empty() && headerFilename.empty() && javaFilename.empty()) {
+ if (cppFilename.empty() && headerFilename.empty()
+ && javaFilename.empty() && rustFilename.empty()) {
print_usage();
return 1;
}
@@ -278,6 +289,20 @@
fclose(out);
}
+ // Write the .rs file
+ if (!rustFilename.empty()) {
+ FILE* out = fopen(rustFilename.c_str(), "w");
+ if (out == nullptr) {
+ fprintf(stderr, "Unable to open file for write: %s\n", rustFilename.c_str());
+ return 1;
+ }
+
+ errorCount += android::stats_log_api_gen::write_stats_log_rust(
+ out, atoms, attributionDecl, minApiLevel);
+
+ fclose(out);
+ }
+
return errorCount;
}
diff --git a/stats/stats_log_api_gen/rust_wrapper.rs b/stats/stats_log_api_gen/rust_wrapper.rs
new file mode 100644
index 0000000..44a791e
--- /dev/null
+++ b/stats/stats_log_api_gen/rust_wrapper.rs
@@ -0,0 +1,4 @@
+#![allow(clippy::too_many_arguments)]
+#![allow(missing_docs)]
+
+include!(concat!(env!("OUT_DIR"), "/statslog.rs"));
diff --git a/stats/stats_log_api_gen/rust_writer.cpp b/stats/stats_log_api_gen/rust_writer.cpp
new file mode 100644
index 0000000..1c90de3
--- /dev/null
+++ b/stats/stats_log_api_gen/rust_writer.cpp
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "rust_writer.h"
+
+#include "utils.h"
+
+// Note that we prepend _ to variable names to avoid using Rust language keyword.
+// E.g., a variable named "type" would not compile.
+
+namespace android {
+namespace stats_log_api_gen {
+
+const char* rust_type_name(java_type_t type, bool lifetime) {
+ switch (type) {
+ case JAVA_TYPE_BOOLEAN:
+ return "bool";
+ case JAVA_TYPE_INT:
+ case JAVA_TYPE_ENUM:
+ return "i32";
+ case JAVA_TYPE_LONG:
+ return "i64";
+ case JAVA_TYPE_FLOAT:
+ return "f32";
+ case JAVA_TYPE_DOUBLE:
+ return "f64";
+ case JAVA_TYPE_STRING:
+ if (lifetime) {
+ return "&'a str";
+ } else {
+ return "&str";
+ }
+ case JAVA_TYPE_BYTE_ARRAY:
+ if (lifetime) {
+ return "&'a [u8]";
+ } else {
+ return "&[u8]";
+ }
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static string make_camel_case_name(const string& str) {
+ string result;
+ const int N = str.size();
+ bool justSawUnderscore = false;
+ for (int i = 0; i < N; i++) {
+ char c = str[i];
+ if (c == '_') {
+ justSawUnderscore = true;
+ // Don't add the underscore to our result
+ } else if (i == 0 || justSawUnderscore) {
+ result += toupper(c);
+ justSawUnderscore = false;
+ } else {
+ result += tolower(c);
+ }
+ }
+ return result;
+}
+
+static string make_snake_case_name(const string& str) {
+ string result;
+ const int N = str.size();
+ for (int i = 0; i < N; i++) {
+ char c = str[i];
+ if (isupper(c)) {
+ if (i > 0) {
+ result += "_";
+ }
+ result += tolower(c);
+ } else {
+ result += c;
+ }
+ }
+ return result;
+}
+
+static string get_variable_name(const string& str) {
+ // From https://doc.rust-lang.org/reference/identifiers.html.
+ if (str == "crate" || str == "self" || str == "super" || str == "Self") {
+ return make_snake_case_name(str) + "_";
+ } else {
+ return "r#" + make_snake_case_name(str);
+ }
+}
+
+static void write_rust_method_signature(FILE* out, const char* namePrefix,
+ const AtomDecl& atomDecl,
+ const AtomDecl& attributionDecl,
+ bool isCode,
+ bool isNonChained) {
+ // To make the generated code pretty, add newlines between arguments.
+ const char* separator = (isCode ? "\n" : " ");
+ if (isCode) {
+ fprintf(out, " pub fn %s(", namePrefix);
+ } else {
+ fprintf(out, " %s::%s(", atomDecl.name.c_str(), namePrefix);
+ }
+ if (isCode) {
+ fprintf(out, "\n");
+ }
+ if (atomDecl.oneOfName == ONEOF_PULLED_ATOM_NAME) {
+ if (isCode) {
+ fprintf(out, " ");
+ }
+ fprintf(out, "pulled_data_: &mut AStatsEventList,%s", separator);
+ }
+ for (int i = 0; i < atomDecl.fields.size(); i++) {
+ const AtomField &atomField = atomDecl.fields[i];
+ const java_type_t& type = atomField.javaType;
+ if (type == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (int j = 0; j < attributionDecl.fields.size(); j++) {
+ const AtomField& chainField = attributionDecl.fields[j];
+ if (isCode) {
+ fprintf(out, " ");
+ }
+ fprintf(out, "%s_chain_: &[%s],%s", chainField.name.c_str(),
+ rust_type_name(chainField.javaType, false), separator);
+ }
+ } else {
+ if (isCode) {
+ fprintf(out, " ");
+ }
+ // Other arguments can have the same name as these non-chained ones,
+ // so append something.
+ if (isNonChained && i < 2) {
+ fprintf(out, "%s_non_chained_", atomField.name.c_str());
+ } else {
+ fprintf(out, "%s", get_variable_name(atomField.name).c_str());
+ }
+ if (type == JAVA_TYPE_ENUM) {
+ fprintf(out, ": %s,%s", make_camel_case_name(atomField.name).c_str(),
+ separator);
+ } else {
+ fprintf(out, ": %s,%s", rust_type_name(type, false), separator);
+ }
+ }
+ }
+ fprintf(out, " ) -> std::result::Result<(), crate::StatsError>");
+ if (isCode) {
+ fprintf(out, " {");
+ }
+ fprintf(out, "\n");
+}
+
+static void write_rust_usage(FILE* out, const string& method_name,
+ const shared_ptr<AtomDecl> atom,
+ const AtomDecl& attributionDecl,
+ bool isNonChained) {
+ // Key value pairs not supported in Rust because they're not supported in native.
+ if (std::find_if(atom->fields.begin(), atom->fields.end(),
+ [](const AtomField &atomField) {
+ return atomField.javaType == JAVA_TYPE_KEY_VALUE_PAIR;
+ }) != atom->fields.end()) {
+ fprintf(out, " // Key value pairs are unsupported in Rust.\n");
+ return;
+ }
+ fprintf(out, " // Definition: ");
+ write_rust_method_signature(out, method_name.c_str(), *atom, attributionDecl,
+ false, isNonChained);
+}
+
+static void write_rust_atom_constants(FILE* out, const Atoms& atoms,
+ const AtomDecl& attributionDecl) {
+ fprintf(out, "// Constants for atom codes.\n");
+ fprintf(out, "#[derive(num_derive::FromPrimitive)]\n");
+ fprintf(out, "pub enum Atoms {\n");
+
+ std::map<int, AtomDeclSet::const_iterator> atom_code_to_non_chained_decl_map;
+ build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
+ for (const shared_ptr<AtomDecl>& atomDecl : atoms.decls) {
+ string constant = make_camel_case_name(atomDecl->name);
+ fprintf(out, "\n");
+ fprintf(out, " // %s %s\n", atomDecl->message.c_str(), atomDecl->name.c_str());
+ write_rust_usage(out, "// stats_write", atomDecl, attributionDecl, false);
+
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atomDecl->code);
+ if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+ write_rust_usage(out, "stats_write_non_chained", *non_chained_decl->second,
+ attributionDecl, true);
+ }
+ fprintf(out, " %s = %d,\n", constant.c_str(), atomDecl->code);
+ }
+
+ fprintf(out, "\n");
+ fprintf(out, "}\n");
+ fprintf(out, "\n");
+}
+
+static void write_rust_atom_constant_values(FILE* out, const shared_ptr<AtomDecl>& atomDecl) {
+ bool hasConstants = false;
+ for (const AtomField& field : atomDecl->fields) {
+ if (field.javaType == JAVA_TYPE_ENUM) {
+ fprintf(out, " #[repr(i32)]\n");
+ fprintf(out, " #[derive(Clone, Copy)]\n");
+ fprintf(out, " pub enum %s {\n", make_camel_case_name(field.name).c_str());
+ for (map<int, string>::const_iterator value = field.enumValues.begin();
+ value != field.enumValues.end(); value++) {
+ fprintf(out, " %s = %d,\n",
+ make_camel_case_name(value->second).c_str(), value->first);
+ }
+ fprintf(out, " }\n");
+ hasConstants = true;
+ }
+ }
+ if (hasConstants) {
+ fprintf(out, "\n");
+ }
+}
+
+static void write_rust_annotation_constants(FILE* out) {
+ fprintf(out, "// Annotation constants.\n");
+ // The annotation names are all prefixed with AnnotationId.
+ // Ideally that would go into the enum name instead,
+ // but I don't want to modify the actual name strings in case they change in the future.
+ fprintf(out, "#[allow(clippy::enum_variant_names)]\n");
+ fprintf(out, "#[repr(u8)]\n");
+ fprintf(out, "enum Annotations {\n");
+
+ const map<AnnotationId, string>& ANNOTATION_ID_CONSTANTS = get_annotation_id_constants();
+ for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+ fprintf(out, " %s = %hhu,\n", make_camel_case_name(name).c_str(), id);
+ }
+ fprintf(out, "}\n\n");
+}
+
+// This is mostly copied from the version in native_writer with some minor changes.
+// Note that argIndex is 1 for the first argument.
+static void write_annotations(FILE* out, int argIndex, const AtomDecl& atomDecl,
+ const string& methodPrefix, const string& methodSuffix) {
+ const map<AnnotationId, string>& ANNOTATION_ID_CONSTANTS = get_annotation_id_constants();
+ auto annotationsIt = atomDecl.fieldNumberToAnnotations.find(argIndex);
+ if (annotationsIt == atomDecl.fieldNumberToAnnotations.end()) {
+ return;
+ }
+ int resetState = -1;
+ int defaultState = -1;
+ for (const shared_ptr<Annotation>& annotation : annotationsIt->second) {
+ const string& annotationConstant = ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
+ switch (annotation->type) {
+ case ANNOTATION_TYPE_INT:
+ if (ANNOTATION_ID_TRIGGER_STATE_RESET == annotation->annotationId) {
+ resetState = annotation->value.intValue;
+ } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
+ defaultState = annotation->value.intValue;
+ } else {
+ fprintf(out, " %saddInt32Annotation(%scrate::Annotations::%s as u8, %d);\n",
+ methodPrefix.c_str(), methodSuffix.c_str(),
+ make_camel_case_name(annotationConstant).c_str(),
+ annotation->value.intValue);
+ }
+ break;
+ case ANNOTATION_TYPE_BOOL:
+ fprintf(out, " %saddBoolAnnotation(%scrate::Annotations::%s as u8, %s);\n",
+ methodPrefix.c_str(), methodSuffix.c_str(),
+ make_camel_case_name(annotationConstant).c_str(),
+ annotation->value.boolValue ? "true" : "false");
+ break;
+ default:
+ break;
+ }
+ }
+ if (defaultState != -1 && resetState != -1) {
+ const string& annotationConstant =
+ ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_TRIGGER_STATE_RESET);
+ const AtomField& field = atomDecl.fields[argIndex - 1];
+ if (field.javaType == JAVA_TYPE_ENUM) {
+ fprintf(out, " if %s as i32 == %d {\n",
+ get_variable_name(field.name).c_str(), resetState);
+ } else {
+ fprintf(out, " if %s == %d {\n",
+ get_variable_name(field.name).c_str(), resetState);
+ }
+ fprintf(out, " %saddInt32Annotation(%scrate::Annotations::%s as u8, %d);\n",
+ methodPrefix.c_str(), methodSuffix.c_str(),
+ make_camel_case_name(annotationConstant).c_str(),
+ defaultState);
+ fprintf(out, " }\n");
+ }
+}
+
+static int write_rust_method_body(FILE* out, const AtomDecl& atomDecl,
+ const AtomDecl& attributionDecl,
+ const int minApiLevel) {
+ fprintf(out, " unsafe {\n");
+ if (minApiLevel == API_Q) {
+ fprintf(stderr, "TODO: Do we need to handle this case?");
+ return 1;
+ }
+ if (atomDecl.oneOfName == ONEOF_PUSHED_ATOM_NAME) {
+ fprintf(out, " let __event = AStatsEvent_obtain();\n");
+ fprintf(out, " let __dropper = crate::AStatsEventDropper(__event);\n");
+ } else {
+ fprintf(out, " let __event = AStatsEventList_addStatsEvent(pulled_data_);\n");
+ }
+ fprintf(out, " AStatsEvent_setAtomId(__event, crate::Atoms::%s as u32);\n",
+ make_camel_case_name(atomDecl.name).c_str());
+ write_annotations(out, ATOM_ID_FIELD_NUMBER, atomDecl, "AStatsEvent_", "__event, ");
+ for (int i = 0; i < atomDecl.fields.size(); i++) {
+ const AtomField& atomField = atomDecl.fields[i];
+ const string& name = get_variable_name(atomField.name);
+ const java_type_t& type = atomField.javaType;
+ switch (type) {
+ case JAVA_TYPE_ATTRIBUTION_CHAIN: {
+ const char* uidName = attributionDecl.fields.front().name.c_str();
+ const char* tagName = attributionDecl.fields.back().name.c_str();
+ fprintf(out,
+ " let uids = %s_chain_.iter().map(|n| (*n).try_into())"
+ ".collect::<std::result::Result<Vec<_>, _>>()?;\n"
+ " let str_arr = %s_chain_.iter().map(|s| std::ffi::CString::new(*s))"
+ ".collect::<std::result::Result<Vec<_>, _>>()?;\n"
+ " let ptr_arr = str_arr.iter().map(|s| s.as_ptr())"
+ ".collect::<std::vec::Vec<_>>();\n"
+ " AStatsEvent_writeAttributionChain(__event, uids.as_ptr(),\n"
+ " ptr_arr.as_ptr(), %s_chain_.len().try_into()?);\n",
+ uidName, tagName, uidName);
+ break;
+ }
+ case JAVA_TYPE_BYTE_ARRAY:
+ fprintf(out,
+ " AStatsEvent_writeByteArray(__event, "
+ "%s.as_ptr(), %s.len());\n",
+ name.c_str(), name.c_str());
+ break;
+ case JAVA_TYPE_BOOLEAN:
+ fprintf(out, " AStatsEvent_writeBool(__event, %s);\n", name.c_str());
+ break;
+ case JAVA_TYPE_ENUM:
+ fprintf(out, " AStatsEvent_writeInt32(__event, %s as i32);\n", name.c_str());
+ break;
+ case JAVA_TYPE_INT:
+ fprintf(out, " AStatsEvent_writeInt32(__event, %s);\n", name.c_str());
+ break;
+ case JAVA_TYPE_FLOAT:
+ fprintf(out, " AStatsEvent_writeFloat(__event, %s);\n", name.c_str());
+ break;
+ case JAVA_TYPE_LONG:
+ fprintf(out, " AStatsEvent_writeInt64(__event, %s);\n", name.c_str());
+ break;
+ case JAVA_TYPE_STRING:
+ fprintf(out, " let str = std::ffi::CString::new(%s)?;\n", name.c_str());
+ fprintf(out, " AStatsEvent_writeString(__event, str.as_ptr());\n");
+ break;
+ default:
+ // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS
+ fprintf(stderr, "Encountered unsupported type: %d.", type);
+ return 1;
+ }
+ // write_annotations expects the first argument to have an index of 1.
+ write_annotations(out, i + 1, atomDecl, "AStatsEvent_", "__event, ");
+ }
+ if (atomDecl.oneOfName == ONEOF_PUSHED_ATOM_NAME) {
+ fprintf(out, " let __ret = AStatsEvent_write(__event);\n");
+ fprintf(out, " if __ret >= 0 { std::result::Result::Ok(()) } else { Err(crate::StatsError::Return(__ret)) }\n");
+ } else {
+ fprintf(out, " AStatsEvent_build(__event);\n");
+ fprintf(out, " std::result::Result::Ok(())\n");
+ }
+ fprintf(out, " }\n");
+ return 0;
+}
+
+static int write_rust_stats_write_method(FILE* out, const shared_ptr<AtomDecl>& atomDecl,
+ const AtomDecl& attributionDecl,
+ const int minApiLevel) {
+ if (atomDecl->oneOfName == ONEOF_PUSHED_ATOM_NAME) {
+ write_rust_method_signature(out, "stats_write", *atomDecl, attributionDecl,
+ true, false);
+ } else {
+ write_rust_method_signature(out, "add_astats_event", *atomDecl, attributionDecl,
+ true, false);
+ }
+ int ret = write_rust_method_body(out, *atomDecl, attributionDecl, minApiLevel);
+ if (ret != 0) {
+ return ret;
+ }
+ fprintf(out, " }\n\n");
+ return 0;
+}
+
+static void write_rust_stats_write_non_chained_method(FILE* out,
+ const shared_ptr<AtomDecl>& atomDecl,
+ const AtomDecl& attributionDecl) {
+ write_rust_method_signature(out, "stats_write_non_chained", *atomDecl, attributionDecl,
+ true, true);
+ fprintf(out, " stats_write(");
+ for (int i = 0; i < atomDecl->fields.size(); i++) {
+ if (i != 0) {
+ fprintf(out, ", ");
+ }
+ const AtomField &atomField = atomDecl->fields[i];
+ const java_type_t& type = atomField.javaType;
+ if (i < 2) {
+ // The first two args are attribution chains.
+ fprintf(out, "&[%s_non_chained_]", atomField.name.c_str());
+ } else if (type == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (int j = 0; j < attributionDecl.fields.size(); j++) {
+ const AtomField& chainField = attributionDecl.fields[j];
+ if (i != 0 || j != 0) {
+ fprintf(out, ", ");
+ }
+ fprintf(out, "&[%s_chain_]", chainField.name.c_str());
+ }
+ } else {
+ fprintf(out, "%s", get_variable_name(atomField.name).c_str());
+ }
+ }
+ fprintf(out, ")\n");
+ fprintf(out, " }\n\n");
+}
+
+static bool needs_lifetime(const shared_ptr<AtomDecl>& atomDecl) {
+ for (const AtomField& atomField : atomDecl->fields) {
+ const java_type_t& type = atomField.javaType;
+ if (type == JAVA_TYPE_ATTRIBUTION_CHAIN
+ || type == JAVA_TYPE_STRING || type == JAVA_TYPE_BYTE_ARRAY) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void write_rust_struct(FILE* out, const shared_ptr<AtomDecl>& atomDecl,
+ const AtomDecl& attributionDecl) {
+ // Write the struct.
+ bool lifetime = needs_lifetime(atomDecl);
+ if (lifetime) {
+ fprintf(out, " pub struct %s<'a> {\n", make_camel_case_name(atomDecl->name).c_str());
+ } else {
+ fprintf(out, " pub struct %s {\n", make_camel_case_name(atomDecl->name).c_str());
+ }
+ for (const AtomField& atomField : atomDecl->fields) {
+ const java_type_t& type = atomField.javaType;
+ if (type == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (const AtomField& chainField : attributionDecl.fields) {
+ fprintf(out, " pub %s_chain_: &'a [%s],\n", chainField.name.c_str(),
+ rust_type_name(chainField.javaType, true));
+ }
+ } else {
+ fprintf(out, " pub %s:", get_variable_name(atomField.name).c_str());
+ if (type == JAVA_TYPE_ENUM) {
+ fprintf(out, " %s,\n", make_camel_case_name(atomField.name).c_str());
+ } else {
+ fprintf(out, " %s,\n", rust_type_name(type, true));
+ }
+ }
+ }
+ fprintf(out, " }\n");
+
+ // Write the impl
+ if (lifetime) {
+ fprintf(out, " impl<'a> %s<'a> {\n", make_camel_case_name(atomDecl->name).c_str());
+ } else {
+ fprintf(out, " impl %s {\n", make_camel_case_name(atomDecl->name).c_str());
+ }
+ fprintf(out, " #[inline(always)]\n");
+ if (atomDecl->oneOfName == ONEOF_PUSHED_ATOM_NAME) {
+ fprintf(out, " pub fn stats_write(&self)"
+ " -> std::result::Result<(), crate::StatsError> {\n");
+ fprintf(out, " stats_write(");
+ } else {
+ fprintf(out, " pub fn add_astats_event(&self, pulled_data: &mut AStatsEventList)"
+ " -> std::result::Result<(), crate::StatsError> {\n");
+ fprintf(out, " add_astats_event(pulled_data, ");
+ }
+ for (const AtomField& atomField : atomDecl->fields) {
+ const java_type_t& type = atomField.javaType;
+ if (type == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (const AtomField& chainField : attributionDecl.fields) {
+ fprintf(out, "self.%s_chain_, ", chainField.name.c_str());
+ }
+ } else {
+ fprintf(out, "self.%s, ", get_variable_name(atomField.name).c_str());
+ }
+ }
+ fprintf(out, ")\n");
+ fprintf(out, " }\n");
+ fprintf(out, " }\n\n");
+}
+
+static int write_rust_stats_write_atoms(FILE* out, const AtomDeclSet& atomDeclSet,
+ const AtomDecl& attributionDecl,
+ const AtomDeclSet& nonChainedAtomDeclSet,
+ const int minApiLevel) {
+ for (const auto &atomDecl : atomDeclSet) {
+ // Key value pairs not supported in Rust because they're not supported in native.
+ if (std::find_if(atomDecl->fields.begin(), atomDecl->fields.end(),
+ [](const AtomField &atomField) {
+ return atomField.javaType == JAVA_TYPE_KEY_VALUE_PAIR;
+ }) != atomDecl->fields.end()) {
+ continue;
+ }
+ fprintf(out, "pub mod %s {\n", atomDecl->name.c_str());
+ fprintf(out, " use statspull_bindgen::*;\n");
+ fprintf(out, " #[allow(unused)]\n");
+ fprintf(out, " use std::convert::TryInto;\n");
+ fprintf(out, "\n");
+ write_rust_atom_constant_values(out, atomDecl);
+ write_rust_struct(out, atomDecl, attributionDecl);
+ int ret = write_rust_stats_write_method(out, atomDecl, attributionDecl, minApiLevel);
+ if (ret != 0) {
+ return ret;
+ }
+ auto nonChained = nonChainedAtomDeclSet.find(atomDecl);
+ if (nonChained != nonChainedAtomDeclSet.end()) {
+ write_rust_stats_write_non_chained_method(out, *nonChained, attributionDecl);
+ }
+ fprintf(out, "}\n");
+ }
+ return 0;
+}
+
+int write_stats_log_rust(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const int minApiLevel) {
+ // Print prelude
+ fprintf(out, "// This file is autogenerated.\n");
+ fprintf(out, "\n");
+ fprintf(out, "#[derive(thiserror::Error, Debug)]\n");
+ fprintf(out, "pub enum StatsError {\n");
+ fprintf(out, " #[error(\"Return error {0:?}\")]\n");
+ fprintf(out, " Return(i32),\n");
+ fprintf(out, " #[error(transparent)]\n");
+ fprintf(out, " NullChar(#[from] std::ffi::NulError),\n");
+ fprintf(out, " #[error(transparent)]\n");
+ fprintf(out, " Conversion(#[from] std::num::TryFromIntError),\n");
+ fprintf(out, "}\n");
+ fprintf(out, "\n");
+ fprintf(out, "struct AStatsEventDropper(*mut statspull_bindgen::AStatsEvent);\n");
+ fprintf(out, "\n");
+ fprintf(out, "impl Drop for AStatsEventDropper {\n");
+ fprintf(out, " fn drop(&mut self) {\n");
+ fprintf(out, " unsafe { statspull_bindgen::AStatsEvent_release(self.0) }\n");
+ fprintf(out, " }\n");
+ fprintf(out, "}\n");
+ fprintf(out, "\n");
+
+ write_rust_atom_constants(out, atoms, attributionDecl);
+ write_rust_annotation_constants(out);
+
+ int errorCount = write_rust_stats_write_atoms(out, atoms.decls, attributionDecl,
+ atoms.non_chained_decls, minApiLevel);
+
+ return errorCount;
+}
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/stats/stats_log_api_gen/rust_writer.h b/stats/stats_log_api_gen/rust_writer.h
new file mode 100644
index 0000000..393e323
--- /dev/null
+++ b/stats/stats_log_api_gen/rust_writer.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdio.h>
+
+#include "Collation.h"
+
+namespace android {
+namespace stats_log_api_gen {
+
+int write_stats_log_rust(FILE* out, const Atoms& atoms, const AtomDecl& attributionDecl,
+ const int minApiLevel);
+
+} // namespace stats_log_api_gen
+} // namespace android
diff --git a/stats/stats_log_api_gen/utils.cpp b/stats/stats_log_api_gen/utils.cpp
index 1eaf42a..d3ea80c 100644
--- a/stats/stats_log_api_gen/utils.cpp
+++ b/stats/stats_log_api_gen/utils.cpp
@@ -40,8 +40,8 @@
return result;
}
-static void build_non_chained_decl_map(const Atoms& atoms,
- std::map<int, AtomDeclSet::const_iterator>* decl_map) {
+void build_non_chained_decl_map(const Atoms& atoms,
+ std::map<int, AtomDeclSet::const_iterator>* decl_map) {
for (AtomDeclSet::const_iterator atomIt = atoms.non_chained_decls.begin();
atomIt != atoms.non_chained_decls.end(); atomIt++) {
decl_map->insert(std::make_pair((*atomIt)->code, atomIt));
diff --git a/stats/stats_log_api_gen/utils.h b/stats/stats_log_api_gen/utils.h
index 22f21c5..947e9fe 100644
--- a/stats/stats_log_api_gen/utils.h
+++ b/stats/stats_log_api_gen/utils.h
@@ -40,6 +40,9 @@
const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02;
const int JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS = 0x04;
+void build_non_chained_decl_map(const Atoms& atoms,
+ std::map<int, AtomDeclSet::const_iterator>* decl_map);
+
const map<AnnotationId, string>& get_annotation_id_constants();
string make_constant_name(const string& str);