Merge "Edit inline comments in .proto files"
diff --git a/include/perfetto/profiling/memory/client_ext.h b/include/perfetto/profiling/memory/client_ext.h
index 2ad34b3..ca4ce2a 100644
--- a/include/perfetto/profiling/memory/client_ext.h
+++ b/include/perfetto/profiling/memory/client_ext.h
@@ -26,10 +26,12 @@
 extern "C" {
 #endif
 
-// This struct is append only. Be very careful that the ABI of this does not
-// change. We want to be able to correctly handle structs from clients that
-// compile against old versions of this header, setting all the newly added
-// fields to zero.
+// Metadata of a custom heap.
+//
+// NB: This struct is append only. Be very careful that the ABI of this does
+// not change. We want to be able to correctly handle structs from clients
+// that compile against old versions of this header, setting all the newly
+// added fields to zero.
 //
 // TODO(fmayer): Sort out alignment etc. before stabilizing the ABI.
 struct HeapprofdHeapInfo {
@@ -38,12 +40,23 @@
   void (*callback)(bool /* enabled */);
 };
 
+// Called by libc upon receipt of the profiling signal.
+// DO NOT CALL FROM OTHER CLIENTS!
 bool heapprofd_init_session(void* (*malloc_fn)(size_t), void (*free_fn)(void*));
 
+// Register a heap. Options are given in the HeapprofdHeapInfo struct.
+//
+// On error, returns 0, which can be safely passed to any function, and will
+// turn them into a no-op.
 uint32_t heapprofd_register_heap(const HeapprofdHeapInfo* heap_info, size_t n);
 
+// Reports an allocation on the given heap.
+// Returns whether the allocation was sampled.
 bool heapprofd_report_allocation(uint32_t heap_id, uint64_t id, uint64_t size);
 
+// Report allocation was freed on the given heap.
+// It is allowed to call with an id that was not previously reported as
+// allocated, in which case it does not change the output.
 void heapprofd_report_free(uint32_t heap_id, uint64_t id);
 
 #ifdef __cplusplus
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 663d578..d43d29c 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -687,6 +687,10 @@
 void HeapGraphTracker::NotifyEndOfFile() {
   if (!sequence_state_.empty()) {
     context_->storage->IncrementStats(stats::heap_graph_non_finalized_graph);
+    // There might still be valuable data even though the trace is truncated.
+    while (!sequence_state_.empty()) {
+      FinalizeProfile(sequence_state_.begin()->first);
+    }
   }
 }
 
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 26c0c14..7ff4f18 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -324,6 +324,10 @@
     return;
   }
   sqlite3_value* value = argv[0];
+  if (sqlite3_value_type(value) == SQLITE_NULL) {
+    sqlite3_result_null(ctx);
+    return;
+  }
   if (sqlite3_value_type(value) != SQLITE_TEXT) {
     sqlite3_result_error(ctx, "Unsupported type of arg passed to DEMANGLE", -1);
     return;
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index 46cf95c..b81977c 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -19,12 +19,16 @@
 import {Controller} from './controller';
 import {App} from './globals';
 
+export function escapeQuery(s: string): string {
+  // See https://www.sqlite.org/lang_expr.html#:~:text=A%20string%20constant
+  return `'%${s.replace('\'', '\'\'')}%'`;
+}
+
 export interface SearchControllerArgs {
   engine: Engine;
   app: App;
 }
 
-
 export class SearchController extends Controller<'main'> {
   private engine: Engine;
   private app: App;
@@ -126,6 +130,8 @@
       resolution: number): Promise<SearchSummary> {
     const quantumNs = Math.round(resolution * 10 * 1e9);
 
+    const searchLiteral = escapeQuery(search);
+
     startNs = Math.floor(startNs / quantumNs) * quantumNs;
 
     await this.query(`update search_summary_window set
@@ -135,8 +141,8 @@
       where rowid = 0;`);
 
     const rawUtidResult = await this.query(`select utid from thread join process
-      using(upid) where thread.name like "%${search}%" or process.name like "%${
-        search}%"`);
+      using(upid) where thread.name like ${searchLiteral}
+      or process.name like ${searchLiteral}`);
 
     const utids = [...rawUtidResult.columns[0].longValues!];
 
@@ -157,7 +163,7 @@
               select
               quantum_ts
               from search_summary_slice_span
-              where name like '%${search}%'
+              where name like ${searchLiteral}
           )
           group by quantum_ts
           order by quantum_ts;`);
@@ -179,6 +185,7 @@
   }
 
   private async specificSearch(search: string) {
+    const searchLiteral = escapeQuery(search);
     // TODO(hjd): we should avoid recomputing this every time. This will be
     // easier once the track table has entries for all the tracks.
     const cpuToTrackId = new Map();
@@ -197,8 +204,9 @@
     }
 
     const rawUtidResult = await this.query(`select utid from thread join process
-    using(upid) where thread.name like "%${search}%" or process.name like "%${
-        search}%"`);
+    using(upid) where
+      thread.name like ${searchLiteral} or
+      process.name like ${searchLiteral}`);
     const utids = [...rawUtidResult.columns[0].longValues!];
 
     const rawResult = await this.query(`
@@ -218,7 +226,7 @@
       0 as utid
       from slice
       inner join track on slice.track_id = track.id
-      and slice.name like '%${search}%'
+      and slice.name like ${searchLiteral}
     order by ts`);
 
     const numRows = +rawResult.numRecords;
diff --git a/ui/src/controller/search_controller_unittest.ts b/ui/src/controller/search_controller_unittest.ts
new file mode 100644
index 0000000..e3774b4
--- /dev/null
+++ b/ui/src/controller/search_controller_unittest.ts
@@ -0,0 +1,21 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {escapeQuery} from './search_controller';
+
+test('escapeQuery', () => {
+  expect(escapeQuery(``)).toEqual(`'%%'`);
+  expect(escapeQuery(`hello`)).toEqual(`'%hello%'`);
+  expect(escapeQuery('foo\'bar')).toEqual(`'%foo''bar%'`);
+});