import unittest from lihont import feed_patch as fp AWEME = "Lcom/ss/android/ugc/aweme/feed/model/Aweme;" FEED_PAGE = """\ .class public Lcom/ss/android/ugc/aweme/feed/model/FeedItemList; .super Ljava/lang/Object; .method public getItems()Ljava/util/List; .registers 4 .annotation system Ldalvik/annotation/Signature; value = { "()", "Ljava/util/List<", "Lcom/ss/android/ugc/aweme/feed/model/Aweme;", ">;" } .end annotation .prologue .line 1 iget-object v0, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->items:Ljava/util/List; return-object v0 .end method .method public getPreloadAds()Ljava/util/List; .registers 2 iget-object v0, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->preloadAds:Ljava/util/List; return-object v0 .end method .method public isHasAd()Z .registers 2 iget-boolean v0, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->hasAd:Z return v0 .end method .method public setItems(Ljava/util/List;)V .registers 4 iput-object p1, p0, Lcom/ss/android/ugc/aweme/feed/model/FeedItemList;->items:Ljava/util/List; return-void .end method """ MID_AD = """\ .class public final Lcom/ss/android/ugc/aweme/commercialize/feed/assem/midad/MidAdResponse; .super Ljava/lang/Object; .method public final getAweme()Lcom/ss/android/ugc/aweme/feed/model/Aweme; .registers 2 iget-object v0, p0, Lcom/ss/android/ugc/aweme/commercialize/feed/assem/midad/MidAdResponse;->aweme:Lcom/ss/android/ugc/aweme/feed/model/Aweme; return-object v0 .end method """ CANDIDATES = """\ .class public final Lcom/ss/android/ugc/aweme/commercialize/feed/candidate/CandidateRequestImpl; .super Ljava/lang/Object; .method public final LIZ(Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V .registers 4 invoke-interface {v0}, Lcom/ss/android/ugc/aweme/commercialize/feed/candidate/CandidateAdRequestApi;->request(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)LX/12b3; return-void .end method """ TOP_VIEW = """\ .class public final Lcom/bytedance/ies/ugc/aweme/commercialize/splash/topview/TopViewJsonManager; .super Ljava/lang/Object; .method public final LIZIZ(Ljava/lang/String;)Lcom/ss/android/ugc/aweme/feed/model/Aweme; .registers 5 invoke-virtual {p0, p1}, Lcom/bytedance/ies/ugc/aweme/commercialize/splash/topview/TopViewJsonManager;->LIZLLL(Ljava/lang/String;)Ljava/util/Map; const/4 v0, 0x0 return-object v0 .end method .method public final declared-synchronized LJ()Ljava/util/Map; .registers 2 .annotation system Ldalvik/annotation/Signature; value = { "()", "Ljava/util/Map<", "Ljava/lang/String;", "LX/0cWK;", ">;" } .end annotation monitor-enter p0 iget-object v0, p0, Lcom/bytedance/ies/ugc/aweme/commercialize/splash/topview/TopViewJsonManager;->LJ:Ljava/util/concurrent/ConcurrentHashMap; monitor-exit p0 return-object v0 .end method """ class HelperTest(unittest.TestCase): def test_parameter_registers_count_this_and_wide_arguments(self): self.assertEqual(1, fp._parameter_registers("public final x()V")) self.assertEqual(0, fp._parameter_registers("public static x()V")) self.assertEqual(4, fp._parameter_registers("public x(IJ)V")) self.assertEqual(4, fp._parameter_registers("public x(Ljava/lang/String;[ILjava/util/List;)V")) self.assertEqual(3, fp._parameter_registers("public static x(DZ)V")) def test_a_local_is_made_when_the_method_has_none(self): self.assertIn(".registers 3", fp._with_local("public x(I)V", " .registers 2\n return-void\n")) self.assertIn(".registers 4", fp._with_local("public x(I)V", " .registers 4\n return-void\n")) def test_the_first_instruction_comes_after_annotations_and_debug_lines(self): body = (" .registers 4\n .annotation system Lx/Y;\n value = {\n }\n .end annotation\n\n" " .prologue\n .line 3\n const/4 v0, 0x1\n") self.assertEqual(" const/4 v0, 0x1\n", body[fp._first_instruction(body):]) class RewriteTest(unittest.TestCase): def test_the_feed_page_is_cleaned_when_read_or_written(self): patched, hits = fp.rewrite_feed_page(FEED_PAGE) self.assertEqual({"items_read", "items_write", "preload_read", "ad_flag"}, set(hits)) self.assertEqual(4, patched.count(fp.MARKER)) self.assertEqual(4, patched.count(fp.CLEAN)) self.assertEqual(1, patched.count(fp.FILTER)) # the call goes after the annotation block and before the method's own code items = patched.split(".method public getItems()")[1].split(".end method")[0] self.assertLess(items.index(".end annotation"), items.index(fp.CLEAN)) self.assertLess(items.index(fp.CLEAN), items.index("iget-object v0")) self.assertEqual(FEED_PAGE.count(".method "), patched.count(".method ")) def test_the_filtered_list_replaces_the_argument_of_set_items(self): patched, _ = fp.rewrite_feed_page(FEED_PAGE) setter = patched.split(".method public setItems(")[1] self.assertLess(setter.index("move-result-object p1"), setter.index("iput-object p1")) def test_the_mid_ad_response_ends_early_while_ads_are_off(self): patched, hits = fp.rewrite_mid_ad(MID_AD) self.assertEqual({"mid_ad": 1}, hits) self.assertLess(patched.index(fp.ADS), patched.index("\n :lihont_keep\n")) self.assertIn("const/4 v0, 0x0\n\n return-object v0", patched) self.assertIn("iget-object v0, p0, Lcom/ss/android/ugc/aweme/commercialize/feed/assem/midad/MidAdResponse;->aweme", patched) def test_the_candidate_request_is_not_sent_while_ads_are_off(self): patched, hits = fp.rewrite_candidates(CANDIDATES) self.assertEqual({"candidate_request": 1}, hits) label = "\n :lihont_keep\n" self.assertLess(patched.index("return-void"), patched.index(label)) self.assertLess(patched.index(label), patched.index("CandidateAdRequestApi")) def test_the_top_view_store_is_empty_while_ads_are_off(self): patched, hits = fp.rewrite_top_view(TOP_VIEW) self.assertEqual({"top_view", "top_view_store"}, set(hits)) store = patched.split("declared-synchronized LJ()")[1] self.assertLess(store.index(fp.ADS), store.index("monitor-enter p0")) self.assertIn("new-instance v0, Ljava/util/HashMap;", store) # LIZIZ(String) has p0 and p1 (two registers) and five in all: v0 is already a local there self.assertIn(".registers 5", patched) def test_a_method_without_a_local_gets_one(self): tight = MID_AD.replace(".registers 2", ".registers 1").replace("iget-object v0, p0,", "iget-object p0, p0,").replace("return-object v0", "return-object p0") patched, _ = fp.rewrite_mid_ad(tight) self.assertIn(".registers 2", patched) def test_a_second_pass_is_refused(self): for rewrite, text in ((fp.rewrite_feed_page, FEED_PAGE), (fp.rewrite_mid_ad, MID_AD), (fp.rewrite_candidates, CANDIDATES), (fp.rewrite_top_view, TOP_VIEW)): with self.assertRaises(ValueError): rewrite(rewrite(text)[0]) def test_a_class_that_changed_is_refused(self): with self.assertRaises(ValueError): fp.rewrite_feed_page(FEED_PAGE.replace("->items:Ljava/util/List;", "->list:Ljava/util/List;")) with self.assertRaises(ValueError): fp.rewrite_feed_page(FEED_PAGE.replace("isHasAd", "hasAnAd")) with self.assertRaises(ValueError): fp.rewrite_mid_ad(MID_AD.replace("->aweme:", "->other:")) with self.assertRaises(ValueError): fp.rewrite_candidates(CANDIDATES.replace("CandidateAdRequestApi", "Other")) with self.assertRaises(ValueError): fp.rewrite_top_view(TOP_VIEW.replace("->LJ:Ljava/util/concurrent/ConcurrentHashMap;", "->LJ:Ljava/util/Map;")) with self.assertRaises(ValueError): fp.rewrite_feed_page(MID_AD) def test_every_rewrite_has_its_dex_and_runtime_calls(self): self.assertEqual(set(fp.REWRITES), set(fp.DEXES)) both = "".join(fp.REWRITES[owner](text)[0] for owner, text in ( (fp.FEED_PAGE, FEED_PAGE), (fp.MID_AD, MID_AD), (fp.CANDIDATES, CANDIDATES), (fp.TOP_VIEW, TOP_VIEW))) for target in fp.runtime_targets(): self.assertIn(target, both) if __name__ == "__main__": unittest.main()