Add api mapping dump feature to AIDL files

The aidl compiler can process files and dump mappings between JNI
signatures of generated methods and their corresponding position in
.aidl source files.

Test: make framework-aidl-mappings

(cherry picked from commit 390cb8bec01c9a4188a4c9472d07f73ada91af95)
Merged-In: I256cfc50967a1781f6a26064263d5f623612d40e
Change-Id: I01fc4a6e350bd3668f27667860de0763b093c587
diff --git a/Android.bp b/Android.bp
index 8cb298d..0deee8d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -57,6 +57,7 @@
         "generate_ndk.cpp",
         "generate_java.cpp",
         "generate_java_binder.cpp",
+        "generate_aidl_mappings.cpp",
         "import_resolver.cpp",
         "line_reader.cpp",
         "io_delegate.cpp",
diff --git a/aidl.cpp b/aidl.cpp
index 227ba16..6fc4aa3 100644
--- a/aidl.cpp
+++ b/aidl.cpp
@@ -38,6 +38,7 @@
 
 #include "aidl_language.h"
 #include "aidl_typenames.h"
+#include "generate_aidl_mappings.h"
 #include "generate_cpp.h"
 #include "generate_java.h"
 #include "generate_ndk.h"
@@ -773,6 +774,34 @@
   return 0;
 }
 
+bool dump_mappings(const Options& options, const IoDelegate& io_delegate) {
+  android::aidl::mappings::SignatureMap all_mappings;
+  for (const string& input_file : options.InputFiles()) {
+    java::JavaTypeNamespace java_types;
+    java_types.Init();
+    vector<AidlDefinedType*> defined_types;
+    vector<string> imported_files;
+
+    AidlError aidl_err = internals::load_and_validate_aidl(
+        input_file, options, io_delegate, &java_types, &defined_types, &imported_files);
+    if (aidl_err != AidlError::OK) {
+      LOG(WARNING) << "AIDL file is invalid.\n";
+      continue;
+    }
+    for (const auto defined_type : defined_types) {
+      auto mappings = mappings::generate_mappings(defined_type);
+      all_mappings.insert(mappings.begin(), mappings.end());
+    }
+  }
+  std::stringstream mappings_str;
+  for (const auto& mapping : all_mappings) {
+    mappings_str << mapping.first << "\n" << mapping.second << "\n";
+  }
+  auto code_writer = io_delegate.GetCodeWriter(options.OutputFile());
+  code_writer->Write("%s", mappings_str.str().c_str());
+  return true;
+}
+
 bool preprocess_aidl(const Options& options, const IoDelegate& io_delegate) {
   unique_ptr<CodeWriter> writer = io_delegate.GetCodeWriter(options.OutputFile());
 
diff --git a/aidl.h b/aidl.h
index 965aa71..7b5628f 100644
--- a/aidl.h
+++ b/aidl.h
@@ -49,6 +49,7 @@
 int compile_aidl(const Options& options, const IoDelegate& io_delegate);
 bool preprocess_aidl(const Options& options, const IoDelegate& io_delegate);
 bool dump_api(const Options& options, const IoDelegate& io_delegate);
+bool dump_mappings(const Options& options, const IoDelegate& io_delegate);
 
 const string kGetInterfaceVersion("getInterfaceVersion");
 
diff --git a/aidl_language.cpp b/aidl_language.cpp
index 9ee3d05..547816a 100644
--- a/aidl_language.cpp
+++ b/aidl_language.cpp
@@ -64,6 +64,12 @@
 
 AidlNode::AidlNode(const AidlLocation& location) : location_(location) {}
 
+std::string AidlNode::PrintLocation() const {
+  std::stringstream ss;
+  ss << location_.file_ << ":" << location_.begin_.line;
+  return ss.str();
+}
+
 AidlError::AidlError(bool fatal) : os_(std::cerr), fatal_(fatal) {
   os_ << "ERROR: ";
 }
diff --git a/aidl_language.h b/aidl_language.h
index 1793f65..49a4606 100644
--- a/aidl_language.h
+++ b/aidl_language.h
@@ -22,6 +22,16 @@
 using std::unique_ptr;
 using std::vector;
 
+class AidlNode;
+
+namespace android {
+namespace aidl {
+namespace mappings {
+std::string dump_location(const AidlNode& method);
+}  // namespace mappings
+}  // namespace aidl
+}  // namespace android
+
 class AidlToken {
  public:
   AidlToken(const std::string& text, const std::string& comments);
@@ -46,6 +56,7 @@
   AidlLocation(const std::string& file, Point begin, Point end);
 
   friend std::ostream& operator<<(std::ostream& os, const AidlLocation& l);
+  friend class AidlNode;
 
  private:
   const std::string file_;
@@ -75,8 +86,10 @@
 
   // To be able to print AidlLocation (nothing else should use this information)
   friend class AidlError;
+  friend std::string android::aidl::mappings::dump_location(const AidlNode&);
 
  private:
+  std::string PrintLocation() const;
   const AidlLocation location_;
 };
 
diff --git a/build/aidl_interface.go b/build/aidl_interface.go
index 4014ac5..5ba98c1 100644
--- a/build/aidl_interface.go
+++ b/build/aidl_interface.go
@@ -70,6 +70,13 @@
 		CommandDeps: []string{"${aidlCmd}"},
 	}, "imports")
 
+	aidlDumpMappingsRule = pctx.StaticRule("aidlDumpMappingsRule", blueprint.RuleParams{
+		Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` +
+			`${aidlCmd} --apimapping ${outDir}/intermediate.txt ${in} ${imports} && ` +
+			`${aidlToJniCmd} ${outDir}/intermediate.txt ${out}`,
+		CommandDeps: []string{"${aidlCmd}"},
+	}, "imports", "outDir")
+
 	aidlFreezeApiRule = pctx.AndroidStaticRule("aidlFreezeApiRule",
 		blueprint.RuleParams{
 			Command: `mkdir -p ${to} && rm -rf ${to}/* && ` +
@@ -88,7 +95,9 @@
 func init() {
 	pctx.HostBinToolVariable("aidlCmd", "aidl")
 	pctx.HostBinToolVariable("bpmodifyCmd", "bpmodify")
+	pctx.SourcePathVariable("aidlToJniCmd", "system/tools/aidl/build/aidl_to_jni.py")
 	android.RegisterModuleType("aidl_interface", aidlInterfaceFactory)
+	android.RegisterModuleType("aidl_mapping", aidlMappingFactory)
 }
 
 // wrap(p, a, s) = [p + v + s for v in a]
@@ -832,3 +841,101 @@
 	}
 	return nil
 }
+
+type aidlMappingProperties struct {
+	// Source file of this prebuilt.
+	Srcs   []string `android:"arch_variant"`
+	Output string
+}
+
+type aidlMapping struct {
+	android.ModuleBase
+	properties     aidlMappingProperties
+	outputFilePath android.WritablePath
+}
+
+func (s *aidlMapping) DepsMutator(ctx android.BottomUpMutatorContext) {
+	android.ExtractSourcesDeps(ctx, s.properties.Srcs)
+}
+
+func addItemsToMap(dest map[string]bool, src []string) {
+	for _, item := range src {
+		dest[item] = true
+	}
+}
+
+func (s *aidlMapping) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var srcs android.Paths
+	var all_import_dirs map[string]bool = make(map[string]bool)
+
+	ctx.VisitDirectDeps(func(module android.Module) {
+		for _, property := range module.GetProperties() {
+			if jproperty, ok := property.(*java.CompilerProperties); ok {
+				for _, src := range jproperty.Srcs {
+					if strings.HasSuffix(src, ".aidl") {
+						full_path := android.PathForModuleSrc(ctx, src)
+						srcs = append(srcs, full_path)
+						all_import_dirs[filepath.Dir(full_path.String())] = true
+					} else if pathtools.IsGlob(src) {
+						globbedSrcFiles, err := ctx.GlobWithDeps(src, nil)
+						if err == nil {
+							for _, globbedSrc := range globbedSrcFiles {
+								full_path := android.PathForModuleSrc(ctx, globbedSrc)
+								all_import_dirs[full_path.String()] = true
+							}
+						}
+					}
+				}
+			} else if jproperty, ok := property.(*java.CompilerDeviceProperties); ok {
+				addItemsToMap(all_import_dirs, jproperty.Aidl.Include_dirs)
+				for _, include_dir := range jproperty.Aidl.Export_include_dirs {
+					var full_path = filepath.Join(ctx.ModuleDir(), include_dir)
+					all_import_dirs[full_path] = true
+				}
+				for _, include_dir := range jproperty.Aidl.Local_include_dirs {
+					var full_path = filepath.Join(ctx.ModuleSubDir(), include_dir)
+					all_import_dirs[full_path] = true
+				}
+			}
+		}
+	})
+
+	var import_dirs []string
+	for dir := range all_import_dirs {
+		import_dirs = append(import_dirs, dir)
+	}
+	imports := strings.Join(wrap("-I", import_dirs, ""), " ")
+	s.outputFilePath = android.PathForModuleOut(ctx, s.properties.Output)
+	outDir := android.PathForModuleGen(ctx)
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   aidlDumpMappingsRule,
+		Inputs: srcs,
+		Output: s.outputFilePath,
+		Args: map[string]string{
+			"imports": imports,
+			"outDir":  outDir.String(),
+		},
+	})
+}
+
+func InitAidlMappingModule(s *aidlMapping) {
+	s.AddProperties(&s.properties)
+}
+
+func aidlMappingFactory() android.Module {
+	module := &aidlMapping{}
+	InitAidlMappingModule(module)
+	android.InitAndroidModule(module)
+	return module
+}
+
+func (m *aidlMapping) AndroidMk() android.AndroidMkData {
+	return android.AndroidMkData{
+		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+			android.WriteAndroidMkData(w, data)
+			targetName := m.Name()
+			fmt.Fprintln(w, ".PHONY:", targetName)
+			fmt.Fprintln(w, targetName+":", m.outputFilePath.String())
+		},
+	}
+}
diff --git a/build/aidl_to_jni.py b/build/aidl_to_jni.py
new file mode 100755
index 0000000..55b1d52
--- /dev/null
+++ b/build/aidl_to_jni.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+
+#
+# Copyright (C) 2019 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.
+#
+
+import sys
+
+DEFAULT_TYPES_TO_JNI = {
+  "boolean": "Z",
+  "byte": "B",
+  "char": "C",
+  "short": "S",
+  "int": "I",
+  "long": "J",
+  "float": "F",
+  "double": "D",
+  "void": "V",
+  "String": "Ljava/lang/String;"
+}
+
+class AIDLMalformedSignatureException(Exception):
+  """Line containing AIDL signature is invalid"""
+
+def convert_type(aidl_type):
+  if aidl_type.endswith("[]"):
+    return "[" + convert_type(aidl_type[:-2])
+  if aidl_type in DEFAULT_TYPES_TO_JNI:
+    return DEFAULT_TYPES_TO_JNI[aidl_type]
+  elif aidl_type.startswith("List<") | aidl_type.startswith("java.util.List<"):
+    return "Ljava/util/List;"
+  else:
+    return "L" + aidl_type.replace(".", "/") + ";"
+
+def convert_method(aidl_signature):
+  aidl_signature = aidl_signature.split("|")
+  if len(aidl_signature) != 4:
+    raise AIDLMalformedSignatureException()
+  class_name, method_name, args, return_type = aidl_signature
+  # Filter out empty arguments since there will be trailing commas
+  args = [x for x in args.split(",") if x]
+  jni_signature = convert_type(class_name)
+  jni_signature += "->"
+  jni_signature += method_name
+  jni_signature += "("
+  params = [convert_type(x) for x in args]
+  jni_signature += "".join(params)
+  jni_signature += ")"
+  jni_signature += convert_type(return_type)
+  return jni_signature
+
+def main(argv):
+  if len(argv) != 3:
+    print("Usage: %s <aidl-mappings> <jni-signature-mappings>" % argv[0])
+    return -1
+
+  aidl_mappings, jni_signature_mappings = argv[1:]
+
+  line_index = 0
+  skip_line = False
+  with open(aidl_mappings) as input_file:
+    with open(jni_signature_mappings, "w") as output_file:
+      for line in input_file:
+        if skip_line:
+          skip_line = False
+        elif line_index % 2 == 1:
+          output_file.write(line)
+        else:
+          try:
+            stripped_line = line.strip()
+            output_file.write(convert_method(line.strip()))
+            output_file.write("\n")
+          except AIDLMalformedSignatureException:
+            print("Malformed signature %s . Skipping..." % stripped_line)
+            # The next line contains the location, need to skip it
+            skip_line = True
+        line_index += 1
+
+if __name__ == "__main__":
+  sys.exit(main(sys.argv))
diff --git a/generate_aidl_mappings.cpp b/generate_aidl_mappings.cpp
new file mode 100644
index 0000000..97958e3
--- /dev/null
+++ b/generate_aidl_mappings.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019, 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 "generate_aidl_mappings.h"
+#include "type_java.h"
+
+#include <sstream>
+
+namespace android {
+namespace aidl {
+namespace mappings {
+
+std::string dump_location(const AidlNode& method) {
+  return method.PrintLocation();
+}
+
+SignatureMap generate_mappings(const AidlDefinedType* defined_type) {
+  const AidlInterface* interface = defined_type->AsInterface();
+  SignatureMap mappings;
+  if (interface == nullptr) {
+    return mappings;
+  }
+  for (const auto& method : interface->GetMethods()) {
+    if (method->IsUserDefined()) {
+      std::stringstream signature;
+      signature << interface->GetCanonicalName() << "|";
+      signature << method->GetName() << "|";
+      for (const auto& arg : method->GetArguments()) {
+        signature << arg->GetType().ToString() << ",";
+      }
+      signature << "|";
+      signature << method->GetType().GetLanguageType<java::Type>()->JavaType();
+      mappings[signature.str()] = dump_location(*method);
+    }
+  }
+  return mappings;
+}
+
+}  // namespace mappings
+}  // namespace aidl
+}  // namespace android
diff --git a/generate_aidl_mappings.h b/generate_aidl_mappings.h
new file mode 100644
index 0000000..09f611e
--- /dev/null
+++ b/generate_aidl_mappings.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019, 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 <string>
+#include <unordered_map>
+#include "aidl_language.h"
+
+namespace android {
+namespace aidl {
+namespace mappings {
+
+using SignatureMap = std::unordered_map<std::string, std::string>;
+
+SignatureMap generate_mappings(const AidlDefinedType* iface);
+}  // namespace mappings
+}  // namespace aidl
+}  // namespace android
diff --git a/main.cpp b/main.cpp
index ffc30a1..d21819a 100644
--- a/main.cpp
+++ b/main.cpp
@@ -50,6 +50,8 @@
       return android::aidl::dump_api(options, io_delegate) ? 0 : 1;
     case Options::Task::CHECK_API:
       return android::aidl::check_api(options, io_delegate) ? 0 : 1;
+    case Options::Task::DUMP_MAPPINGS:
+      return android::aidl::dump_mappings(options, io_delegate) ? 0 : 1;
     default:
       LOG(FATAL) << "aidl: internal error" << std::endl;
       return 1;
diff --git a/options.cpp b/options.cpp
index 8b5f16e..166e8ac 100644
--- a/options.cpp
+++ b/options.cpp
@@ -96,6 +96,14 @@
        << "          tool, that part will not be traced." << endl
        << "  --transaction_names" << endl
        << "          Generate transaction names." << endl
+       << "  --apimapping" << endl
+       << "          Generates a mapping of declared aidl method signatures to" << endl
+       << "          the original line number. e.g.: " << endl
+       << "              If line 39 of foo/bar/IFoo.aidl contains:"
+       << "              void doFoo(int bar, String baz);" << endl
+       << "              Then the result would be:" << endl
+       << "              foo.bar.Baz|doFoo|int,String,|void" << endl
+       << "              foo/bar/IFoo.aidl:39" << endl
        << "  -v VER, --version=VER" << endl
        << "          Set the version of the interface and parcelable to VER." << endl
        << "          VER must be an interger greater than 0." << endl
@@ -152,6 +160,7 @@
         {"dumpapi", no_argument, 0, 'u'},
         {"checkapi", no_argument, 0, 'A'},
 #endif
+        {"apimapping", required_argument, 0, 'i'},
         {"include", required_argument, 0, 'I'},
         {"import", required_argument, 0, 'm'},
         {"preprocessed", required_argument, 0, 'p'},
@@ -279,6 +288,10 @@
       case 'e':
         std::cerr << GetUsage();
         exit(0);
+      case 'i':
+        output_file_ = Trim(optarg);
+        task_ = Task::DUMP_MAPPINGS;
+        break;
       default:
         std::cerr << GetUsage();
         exit(1);
@@ -341,7 +354,7 @@
                        << "got " << (argc - optind) << "." << endl;
         return;
       }
-      if (task_ != Options::Task::CHECK_API) {
+      if (task_ != Options::Task::CHECK_API && task_ != Options::Task::DUMP_MAPPINGS) {
         output_file_ = argv[optind++];
       }
     }
diff --git a/options.h b/options.h
index 11e06ef..403301d 100644
--- a/options.h
+++ b/options.h
@@ -61,7 +61,7 @@
  public:
   enum class Language { UNSPECIFIED, JAVA, CPP, NDK };
 
-  enum class Task { UNSPECIFIED, COMPILE, PREPROCESS, DUMP_API, CHECK_API };
+  enum class Task { UNSPECIFIED, COMPILE, PREPROCESS, DUMP_API, CHECK_API, DUMP_MAPPINGS };
 
   Options(int argc, const char* const argv[], Language default_lang = Language::UNSPECIFIED);
 
@@ -124,6 +124,8 @@
 
   string GetUsage() const;
 
+  bool GenApiMapping() const { return task_ == Task::DUMP_MAPPINGS; }
+
   // The following are for testability, but cannot be influenced on the command line.
   // Threshold of interface methods to enable outlining of onTransact cases.
   size_t onTransact_outline_threshold_{275u};