Create Tweak::ArrayParams for EntryHook
am: fd1dbb6a9c

Change-Id: I31bbb43db7bab380d3b9a3d603cb47ac0e7236c4
diff --git a/dexter/dexter_tests.py b/dexter/dexter_tests.py
index 7a47c01..5e5fe75 100644
--- a/dexter/dexter_tests.py
+++ b/dexter/dexter_tests.py
@@ -26,26 +26,27 @@
 # list of test cases
 # ( <test_name> : { <test_case_config> } )
 test_cases = {
-  'map'             : { 'args' : '-m', 'input' : ['*.dex'] },
-  'stats'           : { 'args' : '-s', 'input' : ['*.dex'] },
-  'asm'             : { 'args' : '-d', 'input' : ['*.dex'] },
-  'hello_stats'     : { 'args' : '-s -e Hello', 'input' : ['hello.dex'] },
-  'am_stats'        : { 'args' : '-s -e android.app.ActivityManager', 'input' : ['large.dex'] },
-  'rewrite'         : { 'args' : '-d -x full_rewrite', 'input' : ['*.dex'] },
-  'entry_hook'      : { 'args' : '-d -x stress_entry_hook', 'input' : [
+  'map'              : { 'args' : '-m', 'input' : ['*.dex'] },
+  'stats'            : { 'args' : '-s', 'input' : ['*.dex'] },
+  'asm'              : { 'args' : '-d', 'input' : ['*.dex'] },
+  'hello_stats'      : { 'args' : '-s -e Hello', 'input' : ['hello.dex'] },
+  'am_stats'         : { 'args' : '-s -e android.app.ActivityManager', 'input' : ['large.dex'] },
+  'rewrite'          : { 'args' : '-d -x full_rewrite', 'input' : ['*.dex'] },
+  'entry_hook'       : { 'args' : '-d -x stress_entry_hook', 'input' : [
                           'entry_hooks.dex', 'hello.dex', 'medium.dex', 'min.dex' ] },
-  'exit_hook'       : { 'args' : '-d -x stress_exit_hook', 'input' : [
+  'exit_hook'        : { 'args' : '-d -x stress_exit_hook', 'input' : [
                           'exit_hooks.dex', 'medium.dex', 'try_catch.dex' ] },
-  'wrap_invoke'     : { 'args' : '-d -x stress_wrap_invoke', 'input' : [
+  'wrap_invoke'      : { 'args' : '-d -x stress_wrap_invoke', 'input' : [
                           'hello.dex', 'hello_nodebug.dex', 'medium.dex' ] },
-  'mi'              : { 'args' : '-d -x test_method_instrumenter', 'input' : ['mi.dex'] },
-  'find_method'     : { 'args' : '-x stress_find_method', 'input' : [
+  'mi'               : { 'args' : '-d -x test_method_instrumenter', 'input' : ['mi.dex'] },
+  'find_method'      : { 'args' : '-x stress_find_method', 'input' : [
                           'hello.dex', 'entry_hooks.dex', 'medium.dex', 'large.dex', 'try_catch.dex' ] },
-  'verbose_cfg'     : { 'args' : '-d --cfg=verbose', 'input' : ['*.dex'] },
-  'compact_cfg'     : { 'args' : '-d --cfg=compact', 'input' : ['*.dex'] },
-  'scratch_regs'    : { 'args' : '-d -x stress_scratch_regs', 'input' : ['*.dex'] },
-  'regs_usage'      : { 'args' : '-x regs_histogram', 'input' : ['*.dex'] },
-  'code_coverage'   : { 'args' : '-d -x code_coverage', 'input' : ['*.dex'] },
+  'verbose_cfg'      : { 'args' : '-d --cfg=verbose', 'input' : ['*.dex'] },
+  'compact_cfg'      : { 'args' : '-d --cfg=compact', 'input' : ['*.dex'] },
+  'scratch_regs'     : { 'args' : '-d -x stress_scratch_regs', 'input' : ['*.dex'] },
+  'regs_usage'       : { 'args' : '-x regs_histogram', 'input' : ['*.dex'] },
+  'code_coverage'    : { 'args' : '-d -x code_coverage', 'input' : ['*.dex'] },
+  'array_entry_hook' : { 'args' : '-d -x array_param_entry_hook', 'input' : ['mi.dex'] },
 }
 
 # run a shell command and returns the stdout content
diff --git a/dexter/experimental.cc b/dexter/experimental.cc
index 545a340..b721883 100644
--- a/dexter/experimental.cc
+++ b/dexter/experimental.cc
@@ -491,6 +491,19 @@
   PrintHistogram(regs_histogram, "Method extra registers (total - parameters)");
 }
 
+// Test slicer::MethodInstrumenter + Tweak::ArrayParams
+void TestArrayParamsEntryHook(std::shared_ptr<ir::DexFile> dex_ir) {
+  slicer::MethodInstrumenter mi(dex_ir);
+  mi.AddTransformation<slicer::EntryHook>(ir::MethodId("LTracer;", "onFooEntry"),
+                                          slicer::EntryHook::Tweak::ArrayParams);
+
+  auto method1 = ir::MethodId("LTarget;", "foo", "(ILjava/lang/String;)I");
+  SLICER_CHECK(mi.InstrumentMethod(method1));
+
+  auto method2 = ir::MethodId("LTarget;", "foo", "(I[[Ljava/lang/String;)Ljava/lang/Integer;");
+  SLICER_CHECK(mi.InstrumentMethod(method2));
+}
+
 void ListExperiments(std::shared_ptr<ir::DexFile> dex_ir);
 
 using Experiment = void (*)(std::shared_ptr<ir::DexFile>);
@@ -507,6 +520,7 @@
     { "stress_scratch_regs", &StressScratchRegs },
     { "regs_histogram", &RegsHistogram },
     { "code_coverage", &CodeCoverage },
+    { "array_param_entry_hook", &TestArrayParamsEntryHook },
 };
 
 // Lists all the registered experiments
diff --git a/slicer/export/slicer/instrumentation.h b/slicer/export/slicer/instrumentation.h
index b9fda8c..bdc04c7 100644
--- a/slicer/export/slicer/instrumentation.h
+++ b/slicer/export/slicer/instrumentation.h
@@ -45,7 +45,12 @@
     // Expose the "this" argument of non-static methods as the "Object" type.
     // This can be helpful when the code you want to handle the hook doesn't
     // have access to the actual type in its classpath.
-    ThisAsObject
+    ThisAsObject,
+    // Forward incoming arguments as an array. Zero-th element of the array is
+    // "this" object if instrumented method isn't static.
+    // It is helpul, when you inject the same hook into the different
+    // methods.
+    ArrayParams,
   };
 
   explicit EntryHook(const ir::MethodId& hook_method_id, Tweak tweak)
@@ -68,6 +73,8 @@
  private:
   ir::MethodId hook_method_id_;
   Tweak tweak_;
+
+  bool InjectArrayParamsHook(lir::CodeIr* code_ir, lir::Bytecode* bytecode);
 };
 
 // Insert a call to the "exit hook" method before every return
diff --git a/slicer/instrumentation.cc b/slicer/instrumentation.cc
index 83a524d..9eaf5da 100644
--- a/slicer/instrumentation.cc
+++ b/slicer/instrumentation.cc
@@ -29,9 +29,88 @@
   }
 };
 
+void BoxValue(lir::Bytecode* bytecode,
+              lir::CodeIr* code_ir,
+              ir::Type* type,
+              dex::u4 src_reg,
+              dex::u4 dst_reg) {
+  bool is_wide = false;
+  const char* boxed_type_name = nullptr;
+  switch (*(type->descriptor)->c_str()) {
+    case 'Z':
+      boxed_type_name = "Ljava/lang/Boolean;";
+      break;
+    case 'B':
+      boxed_type_name = "Ljava/lang/Byte;";
+      break;
+    case 'C':
+      boxed_type_name = "Ljava/lang/Character;";
+      break;
+    case 'S':
+      boxed_type_name = "Ljava/lang/Short;";
+      break;
+    case 'I':
+      boxed_type_name = "Ljava/lang/Integer;";
+      break;
+    case 'J':
+      is_wide = true;
+      boxed_type_name = "Ljava/lang/Long;";
+      break;
+    case 'F':
+      boxed_type_name = "Ljava/lang/Float;";
+      break;
+    case 'D':
+      is_wide = true;
+      boxed_type_name = "Ljava/lang/Double;";
+      break;
+  }
+  SLICER_CHECK(boxed_type_name != nullptr);
+
+  ir::Builder builder(code_ir->dex_ir);
+  std::vector<ir::Type*> param_types;
+  param_types.push_back(type);
+
+  auto boxed_type = builder.GetType(boxed_type_name);
+  auto ir_proto = builder.GetProto(boxed_type, builder.GetTypeList(param_types));
+
+  auto ir_method_decl = builder.GetMethodDecl(
+      builder.GetAsciiString("valueOf"), ir_proto, boxed_type);
+
+  auto boxing_method = code_ir->Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
+
+  auto args = code_ir->Alloc<lir::VRegRange>(src_reg, 1 + is_wide);
+  auto boxing_invoke = code_ir->Alloc<lir::Bytecode>();
+  boxing_invoke->opcode = dex::OP_INVOKE_STATIC_RANGE;
+  boxing_invoke->operands.push_back(args);
+  boxing_invoke->operands.push_back(boxing_method);
+  code_ir->instructions.InsertBefore(bytecode, boxing_invoke);
+
+  auto move_result = code_ir->Alloc<lir::Bytecode>();
+  move_result->opcode = dex::OP_MOVE_RESULT_OBJECT;
+  move_result->operands.push_back(code_ir->Alloc<lir::VReg>(dst_reg));
+  code_ir->instructions.InsertBefore(bytecode, move_result);
+}
+
 }  // namespace
 
 bool EntryHook::Apply(lir::CodeIr* code_ir) {
+  lir::Bytecode* bytecode = nullptr;
+  // find the first bytecode in the method body to insert the hook before it
+  for (auto instr : code_ir->instructions) {
+    BytecodeConvertingVisitor visitor;
+    instr->Accept(&visitor);
+    bytecode = visitor.out;
+    if (bytecode != nullptr) {
+      break;
+    }
+  }
+  if (bytecode == nullptr) {
+    return false;
+  }
+  if (tweak_ == Tweak::ArrayParams) {
+    return InjectArrayParamsHook(code_ir, bytecode);
+  }
+
   ir::Builder builder(code_ir->dex_ir);
   const auto ir_method = code_ir->ir_method;
 
@@ -75,17 +154,116 @@
   hook_invoke->operands.push_back(hook_method);
 
   // insert the hook before the first bytecode in the method body
-  for (auto instr : code_ir->instructions) {
-    BytecodeConvertingVisitor visitor;
-    instr->Accept(&visitor);
-    auto bytecode = visitor.out;
-    if (bytecode == nullptr) {
-      continue;
-    }
-    code_ir->instructions.InsertBefore(bytecode, hook_invoke);
-    break;
+  code_ir->instructions.InsertBefore(bytecode, hook_invoke);
+  return true;
+}
+
+bool EntryHook::InjectArrayParamsHook(lir::CodeIr* code_ir, lir::Bytecode* bytecode) {
+  ir::Builder builder(code_ir->dex_ir);
+  const auto ir_method = code_ir->ir_method;
+  auto param_types = ir_method->decl->prototype->param_types->types;
+  bool is_static = (ir_method->access_flags & dex::kAccStatic) != 0;
+
+  bool needsBoxingReg = false;
+  for (auto type: param_types) {
+    needsBoxingReg |= type->GetCategory() != ir::Type::Category::Reference;
   }
 
+  // allocate scract registers
+  slicer::AllocateScratchRegs alloc_regs(2 + needsBoxingReg);
+  alloc_regs.Apply(code_ir);
+  auto reg_iterator = alloc_regs.ScratchRegs().begin();
+  // register that will store size of during allocation
+  // later will be reused to store index when do "aput"
+  dex::u4 array_size_reg = *(reg_iterator);
+  // register that will store an array that will be passed
+  // as a parameter in entry hook
+  dex::u4 array_reg = *(++reg_iterator);
+  // if we need to boxing, this register stores result of boxing
+  dex::u4 boxing_reg = needsBoxingReg ? *(++reg_iterator) : 0;
+
+  // TODO: handle very "high" registers
+  if (boxing_reg > 0xff) {
+    printf("WARNING: can't instrument method %s.%s%s\n",
+           ir_method->decl->parent->Decl().c_str(),
+           ir_method->decl->name->c_str(),
+           ir_method->decl->prototype->Signature().c_str());
+    return false;
+  }
+
+  // array size bytecode
+  auto const_size_op = code_ir->Alloc<lir::Bytecode>();
+  const_size_op->opcode = dex::OP_CONST;
+  const_size_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_size_reg));
+  const_size_op->operands.push_back(code_ir->Alloc<lir::Const32>(param_types.size() + !is_static));
+  code_ir->instructions.InsertBefore(bytecode, const_size_op);
+
+  // allocate array
+  const auto obj_array_type = builder.GetType("[Ljava/lang/Object;");
+  auto allocate_array_op = code_ir->Alloc<lir::Bytecode>();
+  allocate_array_op->opcode = dex::OP_NEW_ARRAY;
+  allocate_array_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_reg));
+  allocate_array_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_size_reg));
+  allocate_array_op->operands.push_back(
+      code_ir->Alloc<lir::Type>(obj_array_type, obj_array_type->orig_index));
+  code_ir->instructions.InsertBefore(bytecode, allocate_array_op);
+
+  // fill the array with parameters passed into function
+
+  std::vector<ir::Type*> types;
+  if (!is_static) {
+    types.push_back(ir_method->decl->parent);
+  }
+
+  types.insert(types.end(), param_types.begin(), param_types.end());
+
+  // register where params start
+  dex::u4 current_reg = ir_method->code->registers - ir_method->code->ins_count;
+  // reuse not needed anymore register to store indexes
+  dex::u4 array_index_reg = array_size_reg;
+  int i = 0;
+  for (auto type: types) {
+    dex::u4 src_reg = 0;
+    if (type->GetCategory() != ir::Type::Category::Reference) {
+      BoxValue(bytecode, code_ir, type, current_reg, boxing_reg);
+      src_reg = boxing_reg;
+      current_reg += 1 + (type->GetCategory() == ir::Type::Category::WideScalar);
+    } else {
+      src_reg = current_reg;
+      current_reg++;
+    }
+
+    auto index_const_op = code_ir->Alloc<lir::Bytecode>();
+    index_const_op->opcode = dex::OP_CONST;
+    index_const_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_index_reg));
+    index_const_op->operands.push_back(code_ir->Alloc<lir::Const32>(i++));
+    code_ir->instructions.InsertBefore(bytecode, index_const_op);
+
+    auto aput_op = code_ir->Alloc<lir::Bytecode>();
+    aput_op->opcode = dex::OP_APUT_OBJECT;
+    aput_op->operands.push_back(code_ir->Alloc<lir::VReg>(src_reg));
+    aput_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_reg));
+    aput_op->operands.push_back(code_ir->Alloc<lir::VReg>(array_index_reg));
+    code_ir->instructions.InsertBefore(bytecode, aput_op);
+  }
+
+  std::vector<ir::Type*> hook_param_types;
+  hook_param_types.push_back(obj_array_type);
+
+  auto ir_proto = builder.GetProto(builder.GetType("V"),
+                                   builder.GetTypeList(hook_param_types));
+
+  auto ir_method_decl = builder.GetMethodDecl(
+      builder.GetAsciiString(hook_method_id_.method_name), ir_proto,
+      builder.GetType(hook_method_id_.class_descriptor));
+
+  auto hook_method = code_ir->Alloc<lir::Method>(ir_method_decl, ir_method_decl->orig_index);
+  auto args = code_ir->Alloc<lir::VRegRange>(array_reg, 1);
+  auto hook_invoke = code_ir->Alloc<lir::Bytecode>();
+  hook_invoke->opcode = dex::OP_INVOKE_STATIC_RANGE;
+  hook_invoke->operands.push_back(args);
+  hook_invoke->operands.push_back(hook_method);
+  code_ir->instructions.InsertBefore(bytecode, hook_invoke);
   return true;
 }
 
diff --git a/testdata/expected/mi.array_entry_hook b/testdata/expected/mi.array_entry_hook
new file mode 100644
index 0000000..8f2a5e3
--- /dev/null
+++ b/testdata/expected/mi.array_entry_hook
@@ -0,0 +1,99 @@
+
+method Base.<init>():void
+{
+	.src "Target.java"
+	.line 1
+	.prologue_end
+	.line 1
+	    0| invoke-direct {v0}, java.lang.Object.<init>():void
+	    3| return-void
+}
+
+method Base.foo(int, java.lang.String):int
+{
+	.params "?", "?"
+	.src "Target.java"
+	.line 3
+	.prologue_end
+	.line 3
+	    0| const/4 v0, #+0 (0x00000000 | 0.00000)
+	    1| return v0
+}
+
+method IBase.bar(java.lang.String):void
+{
+}
+
+method Target.<init>(Base, IBase):void
+{
+	.params "?", "?"
+	.src "Target.java"
+	.line 15
+	.prologue_end
+	.line 15
+	    0| invoke-direct {v0}, java.lang.Object.<init>():void
+	.line 16
+	    3| iput-object v1, v0, Target.base
+	.line 17
+	    5| iput-object v2, v0, Target.iBase
+	.line 18
+	    7| return-void
+}
+
+method Target.foo(int, java.lang.String):int
+{
+	.params "?", "?"
+	.src "Target.java"
+	.line 21
+	.prologue_end
+	.line 21
+	    0| const v0, #+3 (0x00000003 | 4.20390e-45)
+	    3| new-array v1, v0, java.lang.Object[]
+	    5| const v0, #+0 (0x00000000 | 0.00000)
+	    8| aput-object v5, v1, v0
+	   10| invoke-static/range {v6..v6}, java.lang.Integer.valueOf(int):java.lang.Integer
+	   13| move-result-object v2
+	   14| const v0, #+1 (0x00000001 | 1.40130e-45)
+	   17| aput-object v2, v1, v0
+	   19| const v0, #+2 (0x00000002 | 2.80260e-45)
+	   22| aput-object v7, v1, v0
+	   24| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void
+	   27| iget-object v3, v5, Target.base
+	   29| invoke-virtual {v3,v6,v7}, Base.foo(int, java.lang.String):int
+	   32| move-result v3
+	.line 22
+	   33| iget-object v4, v5, Target.iBase
+	   35| invoke-interface {v4,v7}, IBase.bar(java.lang.String):void
+	.line 23
+	   38| return v3
+}
+
+method Target.foo(int, java.lang.String[][]):java.lang.Integer
+{
+	.params "?", "?"
+	.src "Target.java"
+	.line 27
+	.prologue_end
+	.line 27
+	    0| const v0, #+3 (0x00000003 | 4.20390e-45)
+	    3| new-array v1, v0, java.lang.Object[]
+	    5| const v0, #+0 (0x00000000 | 0.00000)
+	    8| aput-object v5, v1, v0
+	   10| invoke-static/range {v6..v6}, java.lang.Integer.valueOf(int):java.lang.Integer
+	   13| move-result-object v2
+	   14| const v0, #+1 (0x00000001 | 1.40130e-45)
+	   17| aput-object v2, v1, v0
+	   19| const v0, #+2 (0x00000002 | 2.80260e-45)
+	   22| aput-object v7, v1, v0
+	   24| invoke-static/range {v1..v1}, Tracer.onFooEntry(java.lang.Object[]):void
+	   27| iget-object v3, v5, Target.base
+	   29| const-string v4, "foo"
+	   31| invoke-virtual {v3,v6,v4}, Base.foo(int, java.lang.String):int
+	.line 28
+	   34| iget-object v3, v5, Target.iBase
+	   36| const-string v4, "bar"
+	   38| invoke-interface {v3,v4}, IBase.bar(java.lang.String):void
+	.line 29
+	   41| const/4 v3, #+0 (0x00000000 | 0.00000)
+	   42| return-object v3
+}