| #!/usr/bin/env python |
| # |
| # Copyright 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Tests for java_deobfuscate.""" |
| |
| import argparse |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| import unittest |
| |
| # Set by command-line argument. |
| _JAVA_DEOBFUSCATE_PATH = None |
| |
| LINE_PREFIXES = [ |
| '', |
| # logcat -v threadtime |
| '09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ', |
| # logcat |
| 'W/GCM (15158): ', |
| 'W/GCM ( 158): ', |
| ] |
| |
| TEST_MAP = """\ |
| this.was.Deobfuscated -> FOO: |
| int[] FontFamily -> a |
| 1:3:void someMethod(int,android.os.Bundle):65:67 -> bar |
| """ |
| |
| TEST_DATA = [ |
| "Here is a FOO", |
| "Here is a FOO baz", |
| "Here is a \"FOO\" baz", |
| "Here is a \"FOO.bar\" baz", |
| "Here it is: FOO", |
| "Here it is: FOO.bar", |
| "SomeError: SomeFrameworkClass in isTestClass for FOO", |
| "Here is a FOO.bar", |
| "Here is a FOO.bar baz", |
| "END FOO#bar", |
| "new-instance 3810 (LSome/Framework/Class;) in LFOO;", |
| "FOO: Error message", |
| "\tat FOO.bar(PG:1)", |
| "\t at\t FOO.bar\t (\t PG:\t 1\t )", |
| ("Unable to start activity ComponentInfo{garbage.in/here.test}:" |
| " java.lang.NullPointerException: Attempt to invoke interface method 'void" |
| " FOO.bar(int,android.os.Bundle)' on a null object reference"), |
| ("Caused by: java.lang.NullPointerException: Attempt to read from field" |
| " 'int[] FOO.a' on a null object reference"), |
| ] |
| |
| EXPECTED_OUTPUT = [ |
| "Here is a this.was.Deobfuscated", |
| "Here is a FOO baz", |
| "Here is a \"this.was.Deobfuscated\" baz", |
| "Here is a \"this.was.Deobfuscated.someMethod\" baz", |
| "Here it is: this.was.Deobfuscated", |
| "Here it is: this.was.Deobfuscated.someMethod", |
| "SomeError: SomeFrameworkClass in isTestClass for this.was.Deobfuscated", |
| "Here is a this.was.Deobfuscated.someMethod", |
| "Here is a FOO.bar baz", |
| "END this.was.Deobfuscated#someMethod", |
| "new-instance 3810 (LSome/Framework/Class;) in Lthis/was/Deobfuscated;", |
| "this.was.Deobfuscated: Error message", |
| "\tat this.was.Deobfuscated.someMethod(Deobfuscated.java:65)", |
| ("\t at\t this.was.Deobfuscated.someMethod\t " |
| "(\t Deobfuscated.java:\t 65\t )"), |
| ("Unable to start activity ComponentInfo{garbage.in/here.test}:" |
| " java.lang.NullPointerException: Attempt to invoke interface method" |
| " 'void this.was.Deobfuscated.someMethod(int,android.os.Bundle)' on a null" |
| " object reference"), |
| ("Caused by: java.lang.NullPointerException: Attempt to read from field" |
| " 'int[] this.was.Deobfuscated.FontFamily' on a null object reference"), |
| ] |
| TEST_DATA = [s + "\n" for s in TEST_DATA] |
| EXPECTED_OUTPUT = [s + "\n" for s in EXPECTED_OUTPUT] |
| |
| |
| class JavaDeobfuscateTest(unittest.TestCase): |
| |
| def __init__(self, *args, **kwargs): |
| super(JavaDeobfuscateTest, self).__init__(*args, **kwargs) |
| self._map_file = None |
| |
| def setUp(self): |
| self._map_file = tempfile.NamedTemporaryFile() |
| self._map_file.write(TEST_MAP) |
| self._map_file.flush() |
| |
| def tearDown(self): |
| if self._map_file: |
| self._map_file.close() |
| |
| def _testImpl(self, input_lines=None, expected_output_lines=None, |
| prefix=''): |
| self.assertTrue(bool(input_lines) == bool(expected_output_lines)) |
| |
| if not input_lines: |
| input_lines = [prefix + x for x in TEST_DATA] |
| if not expected_output_lines: |
| expected_output_lines = [prefix + x for x in EXPECTED_OUTPUT] |
| |
| cmd = [_JAVA_DEOBFUSCATE_PATH, self._map_file.name] |
| proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| proc_output, _ = proc.communicate(''.join(input_lines)) |
| actual_output_lines = proc_output.splitlines(True) |
| for actual, expected in zip(actual_output_lines, expected_output_lines): |
| self.assertTrue( |
| actual == expected or actual.replace('bar', 'someMethod') == expected, |
| msg=''.join([ |
| 'Deobfuscation failed.\n', |
| ' actual: %s' % actual, |
| ' expected: %s' % expected])) |
| |
| def testNoPrefix(self): |
| self._testImpl(prefix='') |
| |
| def testThreadtimePrefix(self): |
| self._testImpl(prefix='09-08 14:38:35.535 18029 18084 E qcom_sensors_hal: ') |
| |
| def testStandardPrefix(self): |
| self._testImpl(prefix='W/GCM (15158): ') |
| |
| def testStandardPrefixWithPadding(self): |
| self._testImpl(prefix='W/GCM ( 158): ') |
| |
| @unittest.skip('causes java_deobfuscate to hang, see crbug.com/876539') |
| def testIndefiniteHang(self): |
| # Test for crbug.com/876539. |
| self._testImpl( |
| input_lines=[ |
| 'VFY: unable to resolve virtual method 2: LFOO;' |
| + '.onDescendantInvalidated ' |
| + '(Landroid/view/View;Landroid/view/View;)V', |
| ], |
| expected_output_lines=[ |
| 'VFY: unable to resolve virtual method 2: Lthis.was.Deobfuscated;' |
| + '.onDescendantInvalidated ' |
| + '(Landroid/view/View;Landroid/view/View;)V', |
| ]) |
| |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--java-deobfuscate-path', type=os.path.realpath, |
| required=True) |
| known_args, unittest_args = parser.parse_known_args() |
| _JAVA_DEOBFUSCATE_PATH = known_args.java_deobfuscate_path |
| unittest_args = [sys.argv[0]] + unittest_args |
| unittest.main(argv=unittest_args) |