Chromium Code Reviews| Index: runtime/vm/precompiler.cc |
| diff --git a/runtime/vm/precompiler.cc b/runtime/vm/precompiler.cc |
| index 44b1c3ec04b442c2cc751db14b15dd08ed726655..6eec3f89b011c7738c2a438065cbd012399d2ddf 100644 |
| --- a/runtime/vm/precompiler.cc |
| +++ b/runtime/vm/precompiler.cc |
| @@ -489,6 +489,7 @@ void Precompiler::DoCompileAll( |
| BindStaticCalls(); |
| SwitchICCalls(); |
| + Obfuscate(); |
|
rmacnak
2017/08/23 01:16:50
We should clear the obfuscation table from the obj
Vyacheslav Egorov (Google)
2017/08/23 15:54:52
This is done at the end of Obfuscate()
|
| ProgramVisitor::Dedup(); |
| @@ -610,30 +611,7 @@ void Precompiler::PrecompileConstructors() { |
| } |
| } |
| -void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) { |
| - // Note that <rootlibrary>.main is not a root. The appropriate main will be |
| - // discovered through _getMainClosure. |
| - |
| - AddSelector(Symbols::NoSuchMethod()); |
| - |
| - AddSelector(Symbols::Call()); // For speed, not correctness. |
| - |
| - // Allocated from C++. |
| - Class& cls = Class::Handle(Z); |
| - for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) { |
| - ASSERT(isolate()->class_table()->IsValidIndex(cid)); |
| - if (!isolate()->class_table()->HasValidClassAt(cid)) { |
| - continue; |
| - } |
| - if ((cid == kDynamicCid) || (cid == kVoidCid) || |
| - (cid == kFreeListElement) || (cid == kForwardingCorpse)) { |
| - continue; |
| - } |
| - cls = isolate()->class_table()->At(cid); |
| - AddInstantiatedClass(cls); |
| - } |
| - |
| - Dart_QualifiedFunctionName vm_entry_points[] = { |
| +static Dart_QualifiedFunctionName vm_entry_points[] = { |
| // Functions |
| {"dart:core", "::", "_completeDeferredLoads"}, |
| {"dart:core", "::", "identityHashCode"}, |
| @@ -675,7 +653,30 @@ void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) { |
| {"dart:core", "Error", "_stackTrace"}, |
| {"dart:math", "_Random", "_state"}, |
| {NULL, NULL, NULL} // Must be terminated with NULL entries. |
| - }; |
| +}; |
| + |
| +void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) { |
| + // Note that <rootlibrary>.main is not a root. The appropriate main will be |
| + // discovered through _getMainClosure. |
| + |
| + AddSelector(Symbols::NoSuchMethod()); |
| + |
| + AddSelector(Symbols::Call()); // For speed, not correctness. |
| + |
| + // Allocated from C++. |
| + Class& cls = Class::Handle(Z); |
| + for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) { |
| + ASSERT(isolate()->class_table()->IsValidIndex(cid)); |
| + if (!isolate()->class_table()->HasValidClassAt(cid)) { |
| + continue; |
| + } |
| + if ((cid == kDynamicCid) || (cid == kVoidCid) || |
| + (cid == kFreeListElement) || (cid == kForwardingCorpse)) { |
| + continue; |
| + } |
| + cls = isolate()->class_table()->At(cid); |
| + AddInstantiatedClass(cls); |
| + } |
| AddEntryPoints(vm_entry_points); |
| AddEntryPoints(embedder_entry_points); |
| @@ -2278,6 +2279,13 @@ void Precompiler::SwitchICCalls() { |
| #endif |
| } |
| +void Precompiler::Obfuscate() { |
| + // TODO(dartbug.com/XXX) remove URIs from Scripts and Libraries. |
| + |
| + // Discard obfuscation mappings to avoid including them into snapshot. |
| + I->object_store()->set_obfuscation_map(Array::Handle(Z)); |
| +} |
| + |
| void Precompiler::FinalizeAllClasses() { |
| Library& lib = Library::Handle(Z); |
| Class& cls = Class::Handle(Z); |
| @@ -3330,6 +3338,365 @@ RawError* Precompiler::CompileFunction(Precompiler* precompiler, |
| return PrecompileFunctionHelper(precompiler, &pipeline, function, optimized); |
| } |
| +Obfuscator::Obfuscator(Thread* thread, const String& private_key) |
| + : state_(NULL) { |
| + Isolate* isolate = thread->isolate(); |
| + Zone* zone = thread->zone(); |
| + |
| + if (isolate->obfuscate()) { |
| + ObjectStore* store = thread->isolate()->object_store(); |
| + Array& obfuscation_state = Array::Handle(zone, store->obfuscation_map()); |
| + |
| + if (store->obfuscation_map() == Array::null()) { |
| + const int kInitialPrivateCapacity = 256; |
| + obfuscation_state = Array::New(kSavedStateSize); |
| + obfuscation_state.SetAt( |
| + 1, Array::Handle(zone, HashTables::New<ObfuscationMap>( |
| + kInitialPrivateCapacity, Heap::kOld))); |
| + } |
| + |
| + state_ = |
| + new (zone) ObfuscationState(thread, obfuscation_state, private_key); |
| + |
| + if (store->obfuscation_map() == Array::null()) { |
| + InitializeRenamingMap(isolate); |
| + } |
| + } |
| +} |
| + |
| +Obfuscator::~Obfuscator() { |
| + if (state_ != NULL) { |
| + state_->SaveState(); |
| + } |
| +} |
| + |
| +void Obfuscator::InitializeRenamingMap(Isolate* isolate) { |
| + // Prevent renaming of classes and method names mentioned in the |
| + // entry points lists. |
| + PreventRenaming(vm_entry_points); |
| + PreventRenaming(isolate->embedder_entry_points()); |
| + |
| +// Prevent renaming of all pseudo-keywords and operators. |
| +// Note: not all pseudo-keywords are mentioned in DART_KEYWORD_LIST |
| +// (for example 'hide', 'show' and async related keywords are omitted). |
| +// Those are protected from renaming as part of all symbols. |
| +#define PREVENT_RENAMING(name, value, priority, attr) \ |
| + do { \ |
| + if (Token::CanBeOverloaded(Token::name) || \ |
| + ((Token::attr & Token::kPseudoKeyword) != 0)) { \ |
| + PreventRenaming(value); \ |
| + } \ |
| + } while (0); |
| + |
| + DART_TOKEN_LIST(PREVENT_RENAMING) |
| + DART_KEYWORD_LIST(PREVENT_RENAMING) |
| +#undef PREVENT_RENAMING |
| + |
| +// Protect all symbols from renaming. |
| +#define PREVENT_RENAMING(name, value) PreventRenaming(value); |
| + PREDEFINED_SYMBOLS_LIST(PREVENT_RENAMING) |
| +#undef PREVENT_RENAMING |
| + |
| + // Protect NativeFieldWrapperClassX names from being obfuscated. Those |
| + // classes are created manually by the runtime system. |
| + // TODO(dartbug.com/XXX) instead call to Obfuscator::Rename from a place |
| + // where these are created. |
| + PreventRenaming("NativeFieldWrapperClass1"); |
| + PreventRenaming("NativeFieldWrapperClass2"); |
| + PreventRenaming("NativeFieldWrapperClass3"); |
| + PreventRenaming("NativeFieldWrapperClass4"); |
| + |
| +// Prevent renaming of ClassID.cid* fields. These fields are injected by |
| +// runtime. |
| +// TODO(dartbug.com/XXX) instead call to Obfuscator::Rename from a place |
| +// where these are created. |
| +#define CLASS_LIST_WITH_NULL(V) \ |
| + V(Null) \ |
| + CLASS_LIST_NO_OBJECT(V) |
| +#define PREVENT_RENAMING(clazz) PreventRenaming("cid" #clazz); |
| + CLASS_LIST_WITH_NULL(PREVENT_RENAMING) |
| +#undef PREVENT_RENAMING |
| +#undef CLASS_LIST_WITH_NULL |
| + |
| +// Prevent renaming of methods that are looked up by method recognizer. |
| +// TODO(dartbug.com/XXX) instead call to Obfuscator::Rename from a place |
| +// where these are looked up. |
| +#define PREVENT_RENAMING(class_name, function_name, recognized_enum, \ |
| + result_type, fingerprint) \ |
| + do { \ |
| + PreventRenaming(#class_name); \ |
| + PreventRenaming(#function_name); \ |
| + } while (0); |
| + RECOGNIZED_LIST(PREVENT_RENAMING) |
| +#undef PREVENT_RENAMING |
| + |
| +// Prevent renaming of methods that are looked up by method recognizer. |
| +// TODO(dartbug.com/XXX) instead call to Obfuscator::Rename from a place |
| +// where these are looked up. |
| +#define PREVENT_RENAMING(class_name, function_name, recognized_enum, \ |
| + fingerprint) \ |
| + do { \ |
| + PreventRenaming(#class_name); \ |
| + PreventRenaming(#function_name); \ |
| + } while (0); |
| + INLINE_WHITE_LIST(PREVENT_RENAMING) |
| + INLINE_BLACK_LIST(PREVENT_RENAMING) |
| + POLYMORPHIC_TARGET_LIST(PREVENT_RENAMING) |
| +#undef PREVENT_RENAMING |
| + |
| + // These are not mentioned by entry points but are still looked up by name. |
|
rmacnak
2017/08/23 01:16:50
They should be mentioned by the entry points. We'r
Vyacheslav Egorov (Google)
2017/08/23 15:54:52
Done.
|
| + PreventRenaming("_resolveScriptUri"); |
| + PreventRenaming("_printClosure"); |
| + PreventRenaming("_uriBaseClosure"); |
| + PreventRenaming("_isolateId"); |
| + PreventRenaming("_loadPort"); |
| + PreventRenaming("_ip"); |
| + PreventRenaming("_port"); |
| + PreventRenaming("_autoStart"); |
| + PreventRenaming("_originCheckDisabled"); |
| + PreventRenaming("_isWindows"); |
| + PreventRenaming("_isFuchsia"); |
| + PreventRenaming("_traceLoading"); |
| + PreventRenaming("_getWatchSignalInternal"); |
| + PreventRenaming("_signalWatch"); |
| + PreventRenaming("main"); |
| + |
| + // Fast path for common conditional import. See Deobfuscate method. |
| + PreventRenaming("dart"); |
| + PreventRenaming("library"); |
| + PreventRenaming("io"); |
| + PreventRenaming("html"); |
| +} |
| + |
| +RawString* Obfuscator::ObfuscationState::RenameImpl(const String& name, |
| + bool atomic) { |
| + ASSERT(name.IsSymbol()); |
| + |
| + renamed_ ^= renames_.GetOrNull(name); |
| + if (renamed_.IsNull()) { |
| + renamed_ = BuildRename(name, atomic); |
| + renames_.UpdateOrInsert(name, renamed_); |
| + } |
| + return renamed_.raw(); |
| +} |
| + |
| +void Obfuscator::PreventRenaming(Dart_QualifiedFunctionName entry_points[]) { |
| + for (intptr_t i = 0; entry_points[i].function_name != NULL; i++) { |
| + const char* class_name = entry_points[i].class_name; |
| + const char* function_name = entry_points[i].function_name; |
| + |
| + const size_t class_name_len = strlen(class_name); |
| + if (strncmp(function_name, class_name, class_name_len) == 0 && |
| + function_name[class_name_len] == '.') { |
| + const char* ctor_name = function_name + class_name_len + 1; |
| + if (ctor_name[0] != '\0') { |
| + PreventRenaming(ctor_name); |
| + } |
| + } else { |
| + PreventRenaming(function_name); |
| + } |
| + PreventRenaming(class_name); |
| + } |
| +} |
| + |
| +static const char* const kGetterPrefix = "get:"; |
| +static const intptr_t kGetterPrefixLength = strlen(kGetterPrefix); |
| +static const char* const kSetterPrefix = "set:"; |
| +static const intptr_t kSetterPrefixLength = strlen(kSetterPrefix); |
| + |
| +void Obfuscator::PreventRenaming(const char* name) { |
| + const char* dot = strchr(name, '.'); |
| + if (dot != NULL) { |
| + name = dot + 1; |
| + } |
| + |
| + if (name[0] == '\0') { |
| + return; |
| + } |
| + |
| + if (strncmp(name, kGetterPrefix, kGetterPrefixLength) == 0) { |
| + name = name + kGetterPrefixLength; |
| + } else if (strncmp(name, kSetterPrefix, kSetterPrefixLength) == 0) { |
| + name = name + kSetterPrefixLength; |
| + } |
| + |
| + state_->PreventRenaming(name); |
| +} |
| + |
| +void Obfuscator::ObfuscationState::SaveState() { |
| + saved_state_.SetAt(kSavedStateNameIndex, String::Handle(String::New(name_))); |
| + saved_state_.SetAt(kSavedStateRenamesIndex, renames_.Release()); |
| + thread_->isolate()->object_store()->set_obfuscation_map(saved_state_); |
| +} |
| + |
| +void Obfuscator::ObfuscationState::PreventRenaming(const char* name) { |
| + string_ = Symbols::New(thread_, name); |
| + PreventRenaming(string_); |
| +} |
| + |
| +void Obfuscator::ObfuscationState::PreventRenaming(const String& name) { |
| + renames_.UpdateOrInsert(name, name); |
| +} |
| + |
| +void Obfuscator::ObfuscationState::NextName() { |
| + for (intptr_t i = 0;; i++) { |
| + const char digit = name_[i]; |
| + if (digit == '\0') { |
| + name_[i] = 'a'; |
| + } else if (digit < 'Z') { |
| + name_[i]++; |
| + } else if (digit == 'Z') { |
| + name_[i] = 'a'; |
| + continue; |
| + } else if (digit < 'z') { |
| + name_[i]++; |
| + } else { |
| + name_[i] = 'A'; |
| + } |
| + break; |
| + } |
| +} |
| + |
| +RawString* Obfuscator::ObfuscationState::NewAtomicRename( |
| + bool should_be_private) { |
| + do { |
| + NextName(); |
| + renamed_ = Symbols::NewFormatted(thread_, "%s%s", |
| + should_be_private ? "_" : "", name_); |
| + } while (renames_.ContainsKey(renamed_)); |
| + return renamed_.raw(); |
| +} |
| + |
| +RawString* Obfuscator::ObfuscationState::BuildRename(const String& name, |
| + bool atomic) { |
| + const bool is_private = name.CharAt(0) == '_'; |
| + if (!atomic && is_private) { |
| + intptr_t i = 0; |
| + while (i < name.Length() && name.CharAt(i) != '@') { |
| + i++; |
| + } |
| + const intptr_t end = i; |
| + |
| + string_ = Symbols::New(thread_, name, 0, end); |
| + |
| + string_ = RenameImpl(string_, /*atomic=*/true); |
| + return Symbols::FromConcat(thread_, string_, private_key_); |
| + } else { |
| + return NewAtomicRename(is_private); |
| + } |
| +} |
| + |
| +void Obfuscator::ObfuscateSymbolInstance(Thread* thread, |
| + const Instance& symbol) { |
| + const intptr_t kSymbolNameOffset = kWordSize; |
| + |
| + Object& name_value = String::Handle(); |
| + name_value = symbol.RawGetFieldAtOffset(kSymbolNameOffset); |
| + if (!name_value.IsString()) { |
| + // dart.internal.Symbol constructor does not validate its input. |
| + return; |
| + } |
| + |
| + String& name = String::Handle(); |
| + name ^= name_value.raw(); |
| + |
| + // TODO(vegorov) it is quite wasteful to create an obfuscator per-symbol. |
| + Obfuscator obfuscator(thread, /*private_key=*/String::Handle()); |
| + |
| + String& component = String::Handle(); |
| + GrowableHandlePtrArray<const String> renamed(thread->zone(), 2); |
| + |
| + const intptr_t length = name.Length(); |
| + intptr_t i = 0, start = 0; |
| + while (i < length) { |
| + // First look for a '.' in the symbol. |
| + start = i; |
| + while (i < length && name.CharAt(i) != '.') { |
| + i++; |
| + } |
| + const intptr_t end = i; |
| + if (end == length) { |
| + break; |
| + } |
| + |
| + if (start != end) { |
| + component = Symbols::New(thread, name, start, end - start); |
| + component = obfuscator.Rename(component, /*atomic=*/true); |
| + renamed.Add(component); |
| + } |
| + |
| + renamed.Add(Symbols::Dot()); |
| + i++; // Skip '.' |
| + } |
| + |
| + // Handle the last component [start, length). |
| + // If symbol ends up at = and it is not one of '[]=', '==', '<=' or |
| + // '>=' then we treat it as a setter symbol of form 'name=' and |
| + // obfuscate to rename('name') + '='. |
| + const bool is_setter = (length - start) > 1 && |
| + name.CharAt(length - 1) == '=' && |
| + !(name.Equals(Symbols::AssignIndexToken()) || |
| + name.Equals(Symbols::EqualOperator()) || |
| + name.Equals(Symbols::GreaterEqualOperator()) || |
| + name.Equals(Symbols::LessEqualOperator())); |
| + const intptr_t end = length - (is_setter ? 1 : 0); |
| + |
| + if ((start == 0) && (end == length) && name.IsSymbol()) { |
| + component = name.raw(); |
| + } else { |
| + component = Symbols::New(thread, name, start, end - start); |
| + } |
| + component = obfuscator.Rename(component, /*atomic=*/true); |
| + renamed.Add(component); |
| + |
| + if (is_setter) { |
| + renamed.Add(Symbols::Equals()); |
| + } |
| + |
| + name = Symbols::FromConcatAll(thread, renamed); |
| + symbol.RawSetFieldAtOffset(kSymbolNameOffset, name); |
| +} |
| + |
| +void Obfuscator::Deobfuscate(Thread* thread, |
| + const GrowableObjectArray& pieces) { |
| + const Array& obfuscation_state = Array::Handle( |
| + thread->zone(), thread->isolate()->object_store()->obfuscation_map()); |
| + if (obfuscation_state.IsNull()) { |
| + return; |
| + } |
| + |
| + const Array& renames = Array::Handle( |
| + thread->zone(), GetRenamesFromSavedState(obfuscation_state)); |
| + |
| + ObfuscationMap renames_map(renames.raw()); |
| + String& piece = String::Handle(); |
| + for (intptr_t i = 0; i < pieces.Length(); i++) { |
| + piece ^= pieces.At(i); |
| + ASSERT(piece.IsSymbol()); |
| + |
| + // Fast path: skip '.' |
| + if (piece.raw() == Symbols::Dot().raw()) { |
| + continue; |
| + } |
| + |
| + // Fast path: check if piece has an identity obfuscation. |
| + if (renames_map.GetOrNull(piece) == piece.raw()) { |
| + continue; |
| + } |
| + |
| + ObfuscationMap::Iterator it(&renames_map); |
| + while (it.MoveNext()) { |
| + const intptr_t entry = it.Current(); |
| + if (renames_map.GetPayload(entry, 0) == piece.raw()) { |
| + piece ^= renames_map.GetKey(entry); |
| + pieces.SetAt(i, piece); |
| + break; |
| + } |
| + } |
| + } |
| + renames_map.Release(); |
| +} |
| + |
| #endif // DART_PRECOMPILER |
| } // namespace dart |