forked from facebook/redex
-
Notifications
You must be signed in to change notification settings - Fork 0
/
DexUtil.cpp
500 lines (457 loc) · 15.2 KB
/
DexUtil.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "DexUtil.h"
#include "Debug.h"
#include "DexClass.h"
#include "DexLoader.h"
#include "EditableCfgAdapter.h"
#include "ReachableClasses.h"
#include "Resolver.h"
#include "Trace.h"
#include "UnknownVirtuals.h"
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/regex.hpp>
#include <deque>
#include <string_view>
#include <unordered_set>
const DexType* get_init_class_type_demand(const IRInstruction* insn) {
switch (insn->opcode()) {
case OPCODE_INVOKE_STATIC: {
// It's the resolved method that counts
auto method = resolve_method(insn->get_method(), opcode_to_search(insn));
return (method && !assumenosideeffects(method)) ? method->get_class()
: nullptr;
}
case OPCODE_SGET:
case OPCODE_SGET_WIDE:
case OPCODE_SGET_OBJECT:
case OPCODE_SGET_BOOLEAN:
case OPCODE_SGET_BYTE:
case OPCODE_SGET_CHAR:
case OPCODE_SGET_SHORT:
case OPCODE_SPUT:
case OPCODE_SPUT_WIDE:
case OPCODE_SPUT_OBJECT:
case OPCODE_SPUT_BOOLEAN:
case OPCODE_SPUT_BYTE:
case OPCODE_SPUT_CHAR:
case OPCODE_SPUT_SHORT: {
// It's the resolved field that counts
auto field = resolve_field(insn->get_field(), FieldSearch::Static);
return field ? field->get_class() : nullptr;
}
case IOPCODE_INIT_CLASS:
case OPCODE_NEW_INSTANCE: {
return insn->get_type();
}
default:
return nullptr;
}
}
DexAccessFlags merge_visibility(uint32_t vis1, uint32_t vis2) {
vis1 &= VISIBILITY_MASK;
vis2 &= VISIBILITY_MASK;
if ((vis1 & ACC_PUBLIC) || (vis2 & ACC_PUBLIC)) return ACC_PUBLIC;
if (vis1 == 0 || vis2 == 0) return static_cast<DexAccessFlags>(0);
if ((vis1 & ACC_PROTECTED) || (vis2 & ACC_PROTECTED)) return ACC_PROTECTED;
return ACC_PRIVATE;
}
void create_runtime_exception_block(const DexString* except_str,
std::vector<IRInstruction*>& block) {
// clang-format off
// new-instance v0, Ljava/lang/RuntimeException; // type@3852
// const-string v1, "Exception String e.g. Too many args" // string@7a6d
// invoke-direct {v0, v1}, Ljava/lang/RuntimeException;.<init>:(Ljava/lang/String;)V
// throw v0
// clang-format on
auto new_inst = (new IRInstruction(OPCODE_NEW_INSTANCE))
->set_type(type::java_lang_RuntimeException());
new_inst->set_dest(0);
IRInstruction* const_inst =
(new IRInstruction(OPCODE_CONST_STRING))->set_string(except_str);
const_inst->set_dest(1);
auto ret = DexType::make_type("V");
auto arg = DexType::make_type("Ljava/lang/String;");
auto args = DexTypeList::make_type_list({arg});
auto proto = DexProto::make_proto(ret, args);
auto meth = DexMethod::make_method(type::java_lang_RuntimeException(),
DexString::make_string("<init>"), proto);
auto invk = new IRInstruction(OPCODE_INVOKE_DIRECT);
invk->set_method(meth);
invk->set_srcs_size(2);
invk->set_src(0, 0);
invk->set_src(1, 1);
IRInstruction* throwinst = new IRInstruction(OPCODE_THROW);
block.emplace_back(new_inst);
block.emplace_back(const_inst);
block.emplace_back(invk);
block.emplace_back(throwinst);
}
bool passes_args_through(IRInstruction* insn,
const IRCode& code,
int ignore /* = 0 */
) {
size_t src_idx{0};
size_t param_count{0};
for (const auto& mie : InstructionIterable(code.get_param_instructions())) {
auto load_param = mie.insn;
++param_count;
if (src_idx >= insn->srcs_size()) {
continue;
}
if (load_param->dest() != insn->src(src_idx++)) {
return false;
}
}
return insn->srcs_size() + ignore == param_count;
}
Scope build_class_scope(const DexStoresVector& stores) {
return build_class_scope(DexStoreClassesIterator(stores));
}
namespace {
template <typename PrefixIt>
bool starts_with_any_prefix(const DexString* str,
const PrefixIt& begin,
const PrefixIt& end) {
if (str == nullptr) {
return false;
}
auto it = begin;
while (it != end) {
if (boost::algorithm::starts_with(str->str(), *it)) {
return true;
}
it++;
}
return false;
}
} // namespace
Scope build_class_scope_for_packages(
const DexStoresVector& stores,
const std::unordered_set<std::string>& package_names) {
Scope v;
for (auto const& store : stores) {
for (auto& dex : store.get_dexen()) {
for (auto& clazz : dex) {
if (starts_with_any_prefix(clazz->get_deobfuscated_name_or_null(),
package_names.begin(),
package_names.end())) {
v.push_back(clazz);
}
}
}
}
return v;
}
void post_dexen_changes(const Scope& v, DexStoresVector& stores) {
DexStoreClassesIterator iter(stores);
post_dexen_changes(v, iter);
}
void load_root_dexen(DexStore& store,
const std::string& dexen_dir_str,
bool balloon,
bool throw_on_balloon_error,
bool verbose,
int support_dex_version) {
namespace fs = boost::filesystem;
fs::path dexen_dir_path(dexen_dir_str);
// NOLINTNEXTLINE(bugprone-assert-side-effect)
redex_assert(fs::is_directory(dexen_dir_path));
// Discover dex files
auto end = fs::directory_iterator();
std::vector<fs::path> dexen;
for (fs::directory_iterator it(dexen_dir_path); it != end; ++it) {
auto file = it->path();
if (fs::is_regular_file(file) &&
!file.extension().compare(std::string(".dex"))) {
dexen.emplace_back(file);
}
}
/*
* Comparator for dexen filename. 'classes.dex' should sort first,
* followed by [^\d]*[\d]+.dex ordered by N numerically.
*/
auto dex_comparator = [](const fs::path& a, const fs::path& b) {
boost::regex s_dex_regex("[^0-9]*([0-9]+)\\.dex");
auto as = a.filename().string();
auto bs = b.filename().string();
boost::smatch amatch;
boost::smatch bmatch;
bool amatched = boost::regex_match(as, amatch, s_dex_regex);
bool bmatched = boost::regex_match(bs, bmatch, s_dex_regex);
if (!amatched && bmatched) {
return true;
} else if (amatched && !bmatched) {
return false;
} else if (!amatched && !bmatched) {
// Compare strings, probably the same
return strcmp(as.c_str(), bs.c_str()) > 0;
} else {
// Compare captures as integers
auto anum = std::stoi(amatch[1]);
auto bnum = std::stoi(bmatch[1]);
return bnum > anum;
}
};
// Sort all discovered dex files
std::sort(dexen.begin(), dexen.end(), dex_comparator);
// Load all discovered dex files
for (const auto& dex : dexen) {
if (verbose) {
TRACE(MAIN, 1, "Loading %s", dex.string().c_str());
}
// N.B. throaway stats for now
DexClasses classes = load_classes_from_dex(
DexLocation::make_location("dex", dex.string()), balloon,
/* throw_on_balloon_error */ throw_on_balloon_error,
support_dex_version);
store.add_classes(std::move(classes));
}
}
void create_store(const std::string& store_name,
DexStoresVector& stores,
DexClasses classes) {
// First, remove the classes from other stores.
for (auto& store : stores) {
store.remove_classes(classes);
}
// Create a new store and add it to the list of stores.
DexStore store(store_name);
store.set_generated();
store.add_classes(std::move(classes));
stores.emplace_back(std::move(store));
}
void relocate_field(DexField* field, DexType* to_type) {
auto from_cls = type_class(field->get_class());
auto to_cls = type_class(to_type);
from_cls->remove_field(field);
DexFieldSpec spec;
spec.cls = to_type;
field->change(spec, true /* rename on collision */);
to_cls->add_field(field);
}
void relocate_method(DexMethod* method, DexType* to_type) {
auto from_cls = type_class(method->get_class());
auto to_cls = type_class(to_type);
from_cls->remove_method(method);
DexMethodSpec spec;
spec.cls = to_type;
method->change(spec, true /* rename on collision */);
to_cls->add_method(method);
}
VisibilityChanges get_visibility_changes(const DexMethod* method,
DexType* scope) {
return get_visibility_changes(method->get_code(), scope, method);
}
void VisibilityChanges::insert(const VisibilityChanges& other) {
classes.insert(other.classes.begin(), other.classes.end());
fields.insert(other.fields.begin(), other.fields.end());
methods.insert(other.methods.begin(), other.methods.end());
}
void VisibilityChanges::clear() {
classes.clear();
fields.clear();
methods.clear();
}
void VisibilityChanges::apply() const {
for (auto cls : classes) {
set_public(cls);
}
for (auto field : fields) {
set_public(field);
}
for (auto method : methods) {
set_public(method);
}
}
bool VisibilityChanges::empty() const {
return classes.empty() && fields.empty() && methods.empty();
}
namespace {
struct VisibilityChangeGetter {
VisibilityChanges& changes;
DexType* scope;
const DexMethod* effective_caller_resolved_from;
void process_insn(IRInstruction* insn) {
if (insn->has_field()) {
auto cls = type_class(insn->get_field()->get_class());
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
auto field = resolve_field(insn->get_field(),
opcode::is_an_sfield_op(insn->opcode())
? FieldSearch::Static
: FieldSearch::Instance);
if (field != nullptr && field->is_concrete()) {
if (!is_public(field)) {
changes.fields.insert(field);
}
cls = type_class(field->get_class());
if (!is_public(cls)) {
changes.classes.insert(cls);
}
}
} else if (insn->has_method()) {
auto cls = type_class(insn->get_method()->get_class());
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
auto current_method =
resolve_method(insn->get_method(), opcode_to_search(insn),
effective_caller_resolved_from);
if (current_method != nullptr && current_method->is_concrete() &&
(scope == nullptr || current_method->get_class() != scope)) {
if (!is_public(current_method)) {
changes.methods.insert(current_method);
}
cls = type_class(current_method->get_class());
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
}
} else if (insn->has_type()) {
auto type = insn->get_type();
auto cls = type_class(type);
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
}
}
void process_catch_types(const std::vector<DexType*>& types) {
for (auto type : types) {
auto cls = type_class(type);
if (cls != nullptr && !cls->is_external() && !is_public(cls)) {
changes.classes.insert(cls);
}
}
}
};
} // namespace
VisibilityChanges get_visibility_changes(
const IRCode* code,
DexType* scope,
const DexMethod* effective_caller_resolved_from) {
always_assert(code != nullptr);
VisibilityChanges changes;
VisibilityChangeGetter getter{changes, scope, effective_caller_resolved_from};
editable_cfg_adapter::iterate(const_cast<IRCode*>(code),
[&getter](MethodItemEntry& mie) {
getter.process_insn(mie.insn);
return editable_cfg_adapter::LOOP_CONTINUE;
});
std::vector<DexType*> types;
code->gather_catch_types(types);
getter.process_catch_types(types);
return changes;
}
VisibilityChanges get_visibility_changes(
const cfg::ControlFlowGraph& cfg,
DexType* scope,
const DexMethod* effective_caller_resolved_from) {
VisibilityChanges changes;
VisibilityChangeGetter getter{changes, scope, effective_caller_resolved_from};
for (auto& mie :
cfg::InstructionIterable(const_cast<cfg::ControlFlowGraph&>(cfg))) {
getter.process_insn(mie.insn);
}
std::vector<DexType*> types;
cfg.gather_catch_types(types);
getter.process_catch_types(types);
return changes;
}
// Check that visibility / accessibility changes to the current method
// won't need to change a referenced method into a virtual or static one.
bool gather_invoked_methods_that_prevent_relocation(
const DexMethod* method,
std::unordered_set<DexMethodRef*>* methods_preventing_relocation) {
auto code = method->get_code();
always_assert(code);
always_assert(code->editable_cfg_built());
auto& cfg = code->cfg();
bool can_relocate = true;
for (auto& mie : InstructionIterable(cfg)) {
auto insn = mie.insn;
auto opcode = insn->opcode();
if (opcode::is_an_invoke(opcode)) {
auto meth =
resolve_method(insn->get_method(), opcode_to_search(insn), method);
if (!meth && opcode == OPCODE_INVOKE_VIRTUAL &&
unknown_virtuals::is_method_known_to_be_public(insn->get_method())) {
continue;
}
if (meth) {
always_assert(meth->is_def());
if (meth->is_external() && !is_public(meth)) {
meth = nullptr;
} else if (opcode == OPCODE_INVOKE_DIRECT && !method::is_init(meth)) {
meth = nullptr;
}
}
if (!meth) {
can_relocate = false;
if (!methods_preventing_relocation) {
break;
}
methods_preventing_relocation->emplace(insn->get_method());
}
}
}
return can_relocate;
}
bool relocate_method_if_no_changes(DexMethod* method, DexType* to_type) {
if (!gather_invoked_methods_that_prevent_relocation(method)) {
return false;
}
set_public(method);
change_visibility(method, to_type);
relocate_method(method, to_type);
return true;
}
bool is_valid_identifier(std::string_view s) {
if (s.empty()) {
// Identifiers must not be empty.
return false;
}
for (char c : s) {
switch (c) {
// Forbidden characters. This may not work for UTF encodings.
case '/':
case ';':
case '.':
case '[':
return false;
// Allow everything else.
default:
break;
}
}
return true;
}
namespace java_names {
namespace {
bool is_not_idenfitier_character(char ch) {
return ch == '=' || ch == '+' || ch == '|' || ch == '@' || ch == '#' ||
ch == '^' || ch == '&' || ch == '"' || ch == '\'' || ch == '`' ||
ch == '~' || ch == '-';
}
} // namespace
// Differs from above "is_valid_identifier" function since this is for external
// names
bool is_identifier(const std::string_view& ident) {
for (const char& ch : ident) {
// java identifiers can be multi-lingual so membership testing is complex.
// much simpler to test for what is definitely not an identifier and then
// assume everything else is a legal identifier char, accepting that we
// will have false positives.
if (is_deliminator(ch) || is_not_idenfitier_character(ch)) {
return false;
}
}
return true;
}
} // namespace java_names