Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3cc4afc6 authored by Eric Holk's avatar Eric Holk
Browse files

[view_compiler] DexBuilder: add support for string literals and null pointers

Bug: 111895153
Change-Id: If6bdd9a280f6feed175be4da57733f3442e6dd14
parent 24df195e
Loading
Loading
Loading
Loading
+26 −7
Original line number Diff line number Diff line
@@ -49,6 +49,9 @@ std::ostream& operator<<(std::ostream& out, const Instruction::Op& opcode) {
    case Instruction::Op::kReturn:
      out << "kReturn";
      return out;
    case Instruction::Op::kReturnObject:
      out << "kReturnObject";
      return out;
    case Instruction::Op::kMove:
      out << "kMove";
      return out;
@@ -137,6 +140,9 @@ ir::String* DexBuilder::GetOrAddString(const std::string& string) {
    entry = Alloc<ir::String>();
    // +1 for null terminator
    entry->data = slicer::MemView{buffer.get(), header_length + string.size() + 1};
    ::dex::u4 const new_index = dex_file_->strings_indexes.AllocateIndex();
    dex_file_->strings_map[new_index] = entry;
    entry->orig_index = new_index;
    string_data_.push_back(std::move(buffer));
  }
  return entry;
@@ -240,8 +246,9 @@ void MethodBuilder::AddInstruction(Instruction instruction) {

void MethodBuilder::BuildReturn() { AddInstruction(Instruction::OpNoArgs(Op::kReturn)); }

void MethodBuilder::BuildReturn(Value src) {
  AddInstruction(Instruction::OpWithArgs(Op::kReturn, /*destination=*/{}, src));
void MethodBuilder::BuildReturn(Value src, bool is_object) {
  AddInstruction(Instruction::OpWithArgs(
      is_object ? Op::kReturnObject : Op::kReturn, /*destination=*/{}, src));
}

void MethodBuilder::BuildConst4(Value target, int value) {
@@ -249,6 +256,11 @@ void MethodBuilder::BuildConst4(Value target, int value) {
  AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::Immediate(value)));
}

void MethodBuilder::BuildConstString(Value target, const std::string& value) {
  const ir::String* const dex_string = dex_->GetOrAddString(value);
  AddInstruction(Instruction::OpWithArgs(Op::kMove, target, Value::String(dex_string->orig_index)));
}

void MethodBuilder::EncodeInstructions() {
  buffer_.clear();
  for (const auto& instruction : instructions_) {
@@ -259,7 +271,9 @@ void MethodBuilder::EncodeInstructions() {
void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
  switch (instruction.opcode()) {
    case Instruction::Op::kReturn:
      return EncodeReturn(instruction);
      return EncodeReturn(instruction, ::art::Instruction::RETURN);
    case Instruction::Op::kReturnObject:
      return EncodeReturn(instruction, ::art::Instruction::RETURN_OBJECT);
    case Instruction::Op::kMove:
      return EncodeMove(instruction);
    case Instruction::Op::kInvokeVirtual:
@@ -271,15 +285,14 @@ void MethodBuilder::EncodeInstruction(const Instruction& instruction) {
  }
}

void MethodBuilder::EncodeReturn(const Instruction& instruction) {
  DCHECK_EQ(Instruction::Op::kReturn, instruction.opcode());
void MethodBuilder::EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode) {
  DCHECK(!instruction.dest().has_value());
  if (instruction.args().size() == 0) {
    buffer_.push_back(art::Instruction::RETURN_VOID);
  } else {
    DCHECK(instruction.args().size() == 1);
    DCHECK_EQ(1, instruction.args().size());
    size_t source = RegisterValue(instruction.args()[0]);
    buffer_.push_back(art::Instruction::RETURN | source << 8);
    buffer_.push_back(opcode | source << 8);
  }
}

@@ -297,6 +310,12 @@ void MethodBuilder::EncodeMove(const Instruction& instruction) {
    DCHECK_LT(source.value(), 16);
    buffer_.push_back(art::Instruction::CONST_4 | (source.value() << 12) |
                      (RegisterValue(*instruction.dest()) << 8));
  } else if (source.is_string()) {
    constexpr size_t kMaxRegisters = 256;
    DCHECK_LT(RegisterValue(*instruction.dest()), kMaxRegisters);
    DCHECK_LT(source.value(), 65536);  // make sure we don't need a jumbo string
    buffer_.push_back(::art::Instruction::CONST_STRING | (RegisterValue(*instruction.dest()) << 8));
    buffer_.push_back(source.value());
  } else {
    UNIMPLEMENTED(FATAL);
  }
+12 −4
Original line number Diff line number Diff line
@@ -110,18 +110,20 @@ class Value {
  static constexpr Value Local(size_t id) { return Value{id, Kind::kLocalRegister}; }
  static constexpr Value Parameter(size_t id) { return Value{id, Kind::kParameter}; }
  static constexpr Value Immediate(size_t value) { return Value{value, Kind::kImmediate}; }
  static constexpr Value String(size_t value) { return Value{value, Kind::kString}; }
  static constexpr Value Label(size_t id) { return Value{id, Kind::kLabel}; }

  bool is_register() const { return kind_ == Kind::kLocalRegister; }
  bool is_parameter() const { return kind_ == Kind::kParameter; }
  bool is_variable() const { return is_register() || is_parameter(); }
  bool is_immediate() const { return kind_ == Kind::kImmediate; }
  bool is_string() const { return kind_ == Kind::kString; }
  bool is_label() const { return kind_ == Kind::kLabel; }

  size_t value() const { return value_; }

 private:
  enum class Kind { kLocalRegister, kParameter, kImmediate, kLabel };
  enum class Kind { kLocalRegister, kParameter, kImmediate, kString, kLabel };

  const size_t value_;
  const Kind kind_;
@@ -137,7 +139,7 @@ class Instruction {
 public:
  // The operation performed by this instruction. These are virtual instructions that do not
  // correspond exactly to DEX instructions.
  enum class Op { kReturn, kMove, kInvokeVirtual, kBindLabel, kBranchEqz };
  enum class Op { kReturn, kReturnObject, kMove, kInvokeVirtual, kBindLabel, kBranchEqz };

  ////////////////////////
  // Named Constructors //
@@ -210,16 +212,22 @@ class MethodBuilder {

  // return-void
  void BuildReturn();
  void BuildReturn(Value src);
  void BuildReturn(Value src, bool is_object = false);
  // const/4
  void BuildConst4(Value target, int value);
  void BuildConstString(Value target, const std::string& value);

  // TODO: add builders for more instructions

 private:
  void EncodeInstructions();
  void EncodeInstruction(const Instruction& instruction);
  void EncodeReturn(const Instruction& instruction);

  // Encodes a return instruction. For instructions with no return value, the opcode field is
  // ignored. Otherwise, this specifies which return instruction will be used (return,
  // return-object, etc.)
  void EncodeReturn(const Instruction& instruction, ::art::Instruction::Code opcode);

  void EncodeMove(const Instruction& instruction);
  void EncodeInvokeVirtual(const Instruction& instruction);
  void EncodeBranch(art::Instruction::Code op, const Instruction& instruction);
+34 −0
Original line number Diff line number Diff line
@@ -82,4 +82,38 @@ public class DexBuilderTest {
    Method method = clazz.getMethod("backwardsBranch");
    Assert.assertEquals(2, method.invoke(null));
  }

  @Test
  public void returnNull() throws Exception {
    ClassLoader loader = loadDexFile("simple.dex");
    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
    Method method = clazz.getMethod("returnNull");
    Assert.assertEquals(null, method.invoke(null));
  }

  @Test
  public void makeString() throws Exception {
    ClassLoader loader = loadDexFile("simple.dex");
    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
    Method method = clazz.getMethod("makeString");
    Assert.assertEquals("Hello, World!", method.invoke(null));
  }

  @Test
  public void returnStringIfZeroAB() throws Exception {
    ClassLoader loader = loadDexFile("simple.dex");
    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
    Method method = clazz.getMethod("returnStringIfZeroAB", int.class);
    Assert.assertEquals("a", method.invoke(null, 0));
    Assert.assertEquals("b", method.invoke(null, 1));
  }

  @Test
  public void returnStringIfZeroBA() throws Exception {
    ClassLoader loader = loadDexFile("simple.dex");
    Class clazz = loader.loadClass("android.startop.test.testcases.SimpleTests");
    Method method = clazz.getMethod("returnStringIfZeroBA", int.class);
    Assert.assertEquals("b", method.invoke(null, 0));
    Assert.assertEquals("a", method.invoke(null, 1));
  }
}
+65 −0
Original line number Diff line number Diff line
@@ -138,6 +138,71 @@ void GenerateSimpleTestCases(const string& outdir) {
  }(backwardsBranch);
  backwardsBranch.Encode();

  // Test that we can make a null value. Basically:
  //
  // public static String returnNull() { return null; }
  MethodBuilder returnNull{cbuilder.CreateMethod("returnNull", Prototype{string_type})};
  [](MethodBuilder& method) {
    Value zero = method.MakeRegister();
    method.BuildConst4(zero, 0);
    method.BuildReturn(zero, /*is_object=*/true);
  }(returnNull);
  returnNull.Encode();

  // Test that we can make String literals. Basically:
  //
  // public static String makeString() { return "Hello, World!"; }
  MethodBuilder makeString{cbuilder.CreateMethod("makeString", Prototype{string_type})};
  [](MethodBuilder& method) {
    Value string = method.MakeRegister();
    method.BuildConstString(string, "Hello, World!");
    method.BuildReturn(string, /*is_object=*/true);
  }(makeString);
  makeString.Encode();

  // Make sure strings are sorted correctly.
  //
  // int returnStringIfZeroAB(int x) { if (x == 0) { return "a"; } else { return "b"; } }
  MethodBuilder returnStringIfZeroAB{
      cbuilder.CreateMethod("returnStringIfZeroAB", Prototype{string_type, TypeDescriptor::Int()})};
  [&](MethodBuilder& method) {
    Value resultIfZero{method.MakeRegister()};
    Value else_target{method.MakeLabel()};
    method.AddInstruction(Instruction::OpWithArgs(
        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
    // else branch
    method.BuildConstString(resultIfZero, "b");
    method.AddInstruction(
        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
    // then branch
    method.AddInstruction(
        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
    method.BuildConstString(resultIfZero, "a");
    method.AddInstruction(
        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
    method.Encode();
  }(returnStringIfZeroAB);
  // int returnStringIfZeroAB(int x) { if (x == 0) { return "b"; } else { return "a"; } }
  MethodBuilder returnStringIfZeroBA{
      cbuilder.CreateMethod("returnStringIfZeroBA", Prototype{string_type, TypeDescriptor::Int()})};
  [&](MethodBuilder& method) {
    Value resultIfZero{method.MakeRegister()};
    Value else_target{method.MakeLabel()};
    method.AddInstruction(Instruction::OpWithArgs(
        Instruction::Op::kBranchEqz, /*dest=*/{}, Value::Parameter(0), else_target));
    // else branch
    method.BuildConstString(resultIfZero, "a");
    method.AddInstruction(
        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
    // then branch
    method.AddInstruction(
        Instruction::OpWithArgs(Instruction::Op::kBindLabel, /*dest=*/{}, else_target));
    method.BuildConstString(resultIfZero, "b");
    method.AddInstruction(
        Instruction::OpWithArgs(Instruction::Op::kReturnObject, /*dest=*/{}, resultIfZero));
    method.Encode();
  }(returnStringIfZeroBA);

  slicer::MemView image{dex_file.CreateImage()};
  std::ofstream out_file(outdir + "/simple.dex");
  out_file.write(image.ptr<const char>(), image.size());