Index: runtime/vm/precompiler.cc |
diff --git a/runtime/vm/precompiler.cc b/runtime/vm/precompiler.cc |
index 44b1c3ec04b442c2cc751db14b15dd08ed726655..55cd062e9739cbe998a5f1c54bb5d953f8854937 100644 |
--- a/runtime/vm/precompiler.cc |
+++ b/runtime/vm/precompiler.cc |
@@ -45,6 +45,7 @@ |
#include "vm/timeline.h" |
#include "vm/timer.h" |
#include "vm/type_table.h" |
+#include "vm/unicode.h" |
#include "vm/version.h" |
namespace dart { |
@@ -489,6 +490,7 @@ void Precompiler::DoCompileAll( |
BindStaticCalls(); |
SwitchICCalls(); |
+ Obfuscate(); |
ProgramVisitor::Dedup(); |
@@ -610,30 +612,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"}, |
@@ -673,9 +652,33 @@ void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) { |
#endif // !PRODUCT |
// Fields |
{"dart:core", "Error", "_stackTrace"}, |
+ {"dart:core", "::", "_uriBaseClosure"}, |
{"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 +2281,82 @@ void Precompiler::SwitchICCalls() { |
#endif |
} |
+void Precompiler::Obfuscate() { |
+ if (!I->obfuscate()) { |
+ return; |
+ } |
+ |
+ class ScriptsCollector : public ObjectVisitor { |
+ public: |
+ explicit ScriptsCollector(Zone* zone, |
+ GrowableHandlePtrArray<const Script>* scripts) |
+ : script_(Script::Handle(zone)), scripts_(scripts) {} |
+ |
+ void VisitObject(RawObject* obj) { |
+ if (obj->GetClassId() == kScriptCid) { |
+ script_ ^= obj; |
+ scripts_->Add(Script::Cast(script_)); |
+ } |
+ } |
+ |
+ private: |
+ Script& script_; |
+ GrowableHandlePtrArray<const Script>* scripts_; |
+ }; |
+ |
+ GrowableHandlePtrArray<const Script> scripts(Z, 100); |
+ Isolate::Current()->heap()->CollectAllGarbage(); |
+ { |
+ HeapIterationScope his(T); |
+ ScriptsCollector visitor(Z, &scripts); |
+ I->heap()->VisitObjects(&visitor); |
+ } |
+ |
+ { |
+ // Note: when this object is destroyed it will commit obfuscation |
+ // mappings into the ObjectStore. Hence the block around it - to |
+ // ensure that destructor is called before we save obfuscation |
+ // mappings and clear the ObjectStore. |
+ Obfuscator obfuscator(T, /*private_key=*/String::Handle(Z)); |
+ String& str = String::Handle(Z); |
+ for (intptr_t i = 0; i < scripts.length(); i++) { |
+ const Script& script = scripts.At(i); |
+ |
+ str = script.url(); |
+ str = Symbols::New(T, str); |
+ str = obfuscator.Rename(str, /*atomic=*/true); |
+ script.set_url(str); |
+ |
+ str = script.resolved_url(); |
+ str = Symbols::New(T, str); |
+ str = obfuscator.Rename(str, /*atomic=*/true); |
+ script.set_resolved_url(str); |
+ } |
+ |
+ Library& lib = Library::Handle(); |
+ for (intptr_t i = 0; i < libraries_.Length(); i++) { |
+ lib ^= libraries_.At(i); |
+ if (!lib.is_dart_scheme()) { |
+ str = lib.name(); |
+ str = obfuscator.Rename(str, /*atomic=*/true); |
+ lib.set_name(str); |
+ |
+ str = lib.url(); |
+ str = Symbols::New(T, str); |
+ str = obfuscator.Rename(str, /*atomic=*/true); |
+ lib.set_url(str); |
+ } |
+ } |
+ Library::RegisterLibraries(T, libraries_); |
+ } |
+ |
+ // Obfuscation is done. Move obfuscation map into malloced memory. |
+ I->set_obfuscation_map(Obfuscator::SerializeMap(T)); |
+ |
+ // 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 +3409,426 @@ 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()) { |
+ // Nothing to do. |
+ return; |
+ } |
+ |
+ // Create ObfuscationState from ObjectStore::obfusction_map(). |
+ ObjectStore* store = thread->isolate()->object_store(); |
+ Array& obfuscation_state = Array::Handle(zone, store->obfuscation_map()); |
+ |
+ if (store->obfuscation_map() == Array::null()) { |
+ // We are just starting the obfuscation. Create initial state. |
+ 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()) { |
+ // We are just starting the obfuscation. Initialize the renaming map. |
+ // Note: InitializeRenamingMap uses state_. |
+ 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 |
+ |
+ // this is a keyword token unless it occurs in the string interpolation |
+ // which causes it to be obfuscated. |
+ PreventRenaming("this"); |
+ |
+// 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/30524) 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/30524) 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/30524) 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/30524) 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. |
+ // (They are not mentioned in the entry points because we don't need them |
+ // after the compilation) |
+ PreventRenaming("_resolveScriptUri"); |
+ |
+ // Precompiler is looking up "main". |
+ // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place |
+ // where these are created. |
+ 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) { |
+ // For constructor names Class.name skip class name (if any) and a dot. |
+ const char* dot = strchr(name, '.'); |
+ if (dot != NULL) { |
+ name = dot + 1; |
+ } |
+ |
+ // Empty name: do nothing. |
+ if (name[0] == '\0') { |
+ return; |
+ } |
+ |
+ // Skip get: and set: prefixes. |
+ 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() { |
+ // We apply the following rules: |
+ // |
+ // inc(a) = b, ... , inc(z) = A, ..., inc(Z) = a & carry. |
+ // |
+ 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; // Carry. |
+ } 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_); |
+ // Must check if our generated name clashes with something that will |
+ // have an identity renaming. |
+ } while (renames_.GetOrNull(renamed_) == renamed_.raw()); |
+ return renamed_.raw(); |
+} |
+ |
+RawString* Obfuscator::ObfuscationState::BuildRename(const String& name, |
+ bool atomic) { |
+ const bool is_private = name.CharAt(0) == '_'; |
+ if (!atomic && is_private) { |
+ // Find the first '@'. |
+ intptr_t i = 0; |
+ while (i < name.Length() && name.CharAt(i) != '@') { |
+ i++; |
+ } |
+ const intptr_t end = i; |
+ |
+ // Follow the rule: |
+ // |
+ // Rename(_ident@key) = Rename(_ident)@private_key_. |
+ // |
+ 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) { |
+ // Note: this must match dart:internal.Symbol declaration. |
+ 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()); |
+ |
+ // Symbol can be a sequence of identifiers separated by dots. |
+ // We split such symbols into components and obfuscate individual identifiers |
+ // separately. |
+ 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 and follow the rule: |
+ // |
+ // Rename('ident=') = Rename('ident') '=' |
+ // |
+ 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; |
+ } |
+ |
+ // Search through the whole obfuscation map until matching value is found. |
+ // We are using linear search instead of generating a reverse mapping |
+ // because we assume that Deobfuscate() method is almost never called. |
+ 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(); |
+} |
+ |
+static const char* StringToCString(const String& str) { |
+ const intptr_t len = Utf8::Length(str); |
+ char* result = new char[len + 1]; |
+ str.ToUTF8(reinterpret_cast<uint8_t*>(result), len); |
+ result[len] = 0; |
+ return result; |
+} |
+ |
+const char** Obfuscator::SerializeMap(Thread* thread) { |
+ const Array& obfuscation_state = Array::Handle( |
+ thread->zone(), thread->isolate()->object_store()->obfuscation_map()); |
+ if (obfuscation_state.IsNull()) { |
+ return NULL; |
+ } |
+ |
+ const Array& renames = Array::Handle( |
+ thread->zone(), GetRenamesFromSavedState(obfuscation_state)); |
+ ObfuscationMap renames_map(renames.raw()); |
+ |
+ const char** result = new const char*[renames_map.NumOccupied() * 2 + 1]; |
+ intptr_t idx = 0; |
+ String& str = String::Handle(); |
+ |
+ ObfuscationMap::Iterator it(&renames_map); |
+ while (it.MoveNext()) { |
+ const intptr_t entry = it.Current(); |
+ str ^= renames_map.GetKey(entry); |
+ result[idx++] = StringToCString(str); |
+ str ^= renames_map.GetPayload(entry, 0); |
+ result[idx++] = StringToCString(str); |
+ } |
+ result[idx++] = NULL; |
+ renames_map.Release(); |
+ |
+ return result; |
+} |
+ |
#endif // DART_PRECOMPILER |
} // namespace dart |