[gidl] Escape strings for HL/LLCPP

Also:

* Adding a constructor to StringPtr to match std::string construction
* Allowing logs to be emitted from the LLCPP conformance test

This change will support adding UTF8 validation to HLCPP and LLCPP (see
Iea42abd67dc928b4c1326b1a466befcb4b69226c).

Bug: 39686
Test: fx test fidl_llcpp_conformance_test
Change-Id: I9e05173ad551e780bf1301626f096803e5b871c3
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/392133
API-Review: Pascal Perez <pascallouis@google.com>
Commit-Queue: Pascal Perez <pascallouis@google.com>
Reviewed-by: Mitchell Kember <mkember@google.com>
Testability-Review: Mitchell Kember <mkember@google.com>
diff --git a/garnet/go/src/fidl/compiler/backend/common/strings.go b/garnet/go/src/fidl/compiler/backend/common/strings.go
index 6135b8e..dec2e46 100644
--- a/garnet/go/src/fidl/compiler/backend/common/strings.go
+++ b/garnet/go/src/fidl/compiler/backend/common/strings.go
@@ -10,3 +10,19 @@
 	s = strings.ReplaceAll(s, `'`, `\'`)
 	return fmt.Sprintf(`'%s'`, s)
 }
+
+// PrintableASCIIRune reports whether r is a printable ASCII rune, i.e. in the
+// range 0x20 to 0x7E.
+func PrintableASCIIRune(r rune) bool {
+	return 0x20 <= r && r <= 0x7e
+}
+
+// PrintableASCII reports whether s is made of only printable ASCII runes.
+func PrintableASCII(s string) bool {
+	for _, r := range s {
+		if !PrintableASCIIRune(r) {
+			return false
+		}
+	}
+	return true
+}
diff --git a/garnet/go/src/fidl/compiler/backend/common/strings_test.go b/garnet/go/src/fidl/compiler/backend/common/strings_test.go
index d03a190..fcb1c96 100644
--- a/garnet/go/src/fidl/compiler/backend/common/strings_test.go
+++ b/garnet/go/src/fidl/compiler/backend/common/strings_test.go
@@ -46,3 +46,62 @@
 		}
 	}
 }
+
+func TestPrintableASCIIRune(t *testing.T) {
+	// positive cases
+	printableRunes := []rune{
+		'h',
+		'e',
+		'l',
+		'0',
+		rune(0x20),
+		rune(0x7e),
+	}
+	for _, r := range printableRunes {
+		if !PrintableASCIIRune(r) {
+			t.Errorf("expected %x to be a printable rune", r)
+		}
+	}
+
+	// negative cases
+	nonPrintableRunes := []rune{
+		rune(0x00),
+		rune(0x19),
+		rune(0x80),
+		rune(0x4242),
+	}
+	for _, r := range nonPrintableRunes {
+		if PrintableASCIIRune(r) {
+			t.Errorf("did not expect %x to be a printable rune", r)
+		}
+	}
+}
+
+func TestPrintableASCII(t *testing.T) {
+	// positive cases
+	printableStrings := []string{
+		"ahb",
+		"aeb",
+		"alb",
+		"a0b",
+		"a\x20b",
+		"a\x7eb",
+	}
+	for _, s := range printableStrings {
+		if !PrintableASCII(s) {
+			t.Errorf("expected %s to be a printable syring", s)
+		}
+	}
+
+	// negative cases
+	nonPrintableStrings := []string{
+		"a\x00b",
+		"a\x19b",
+		"a\x81b",
+	}
+	for _, s := range nonPrintableStrings {
+		if PrintableASCII(s) {
+			t.Errorf("did not expect %s to be a printable string", s)
+		}
+	}
+}
diff --git a/sdk/lib/fidl/cpp/fidl_cpp_base.api b/sdk/lib/fidl/cpp/fidl_cpp_base.api
index ea8f8bf..5fee118 100644
--- a/sdk/lib/fidl/cpp/fidl_cpp_base.api
+++ b/sdk/lib/fidl/cpp/fidl_cpp_base.api
@@ -6,7 +6,7 @@
   "pkg/fidl_cpp_base/include/lib/fidl/cpp/encoder.h": "8fd3258444569fa8ca251f15c36bed48",
   "pkg/fidl_cpp_base/include/lib/fidl/cpp/internal/logging.h": "6cc184bb613d6539ea5f568927ca06be",
   "pkg/fidl_cpp_base/include/lib/fidl/cpp/object_coding.h": "d38c011c0a9cfdf7f2453676cb1f3f2f",
-  "pkg/fidl_cpp_base/include/lib/fidl/cpp/string.h": "1748fadcfef3c5e5596b02599621d32b",
+  "pkg/fidl_cpp_base/include/lib/fidl/cpp/string.h": "2cf00b1ba5f68bcba03b03c4b1c7935d",
   "pkg/fidl_cpp_base/include/lib/fidl/cpp/traits.h": "ca4dfa90b5f4961c81dbfddda683fe94",
   "pkg/fidl_cpp_base/include/lib/fidl/cpp/transition.h": "ea5fd3c5abf19d39e0dda995f794d148",
   "pkg/fidl_cpp_base/include/lib/fidl/cpp/vector.h": "12e15f28cbef68e04558bdb45d9ecabc"
diff --git a/sdk/lib/fidl/cpp/string.h b/sdk/lib/fidl/cpp/string.h
index dac3e36..7f7ef28 100644
--- a/sdk/lib/fidl/cpp/string.h
+++ b/sdk/lib/fidl/cpp/string.h
@@ -60,6 +60,8 @@
 
   // Construct from string pointers
   StringPtr(const char* value) : fit::optional<std::string>(value) {}
+  StringPtr(const char* value, size_t size)
+      : fit::optional<std::string>(std::string(value, size)) {}
   StringPtr& operator=(const char* value) {
     fit::optional<std::string>::operator=(value);
     return *this;
@@ -172,7 +174,7 @@
   const std::string& operator*() const { return str_; }
 
   FIDL_FIT_OPTIONAL_DEPRECATED("use value_or(\"\")")
-  operator const std::string &() const { return str_; }
+  operator const std::string&() const { return str_; }
 
  private:
   std::string str_;
diff --git a/src/lib/fidl/llcpp/tests/BUILD.gn b/src/lib/fidl/llcpp/tests/BUILD.gn
index f33e074..efae769 100644
--- a/src/lib/fidl/llcpp/tests/BUILD.gn
+++ b/src/lib/fidl/llcpp/tests/BUILD.gn
@@ -38,7 +38,7 @@
   ]
 }
 
-unittest_package("fidl_llcpp_conformance_test") {
+test_package("fidl_llcpp_conformance_test") {
   deps = [ ":fidl_llcpp_conformance_test_bin" ]
 
   tests = [
diff --git a/src/lib/fidl/llcpp/tests/meta/fidl_llcpp_conformance_test.cmx b/src/lib/fidl/llcpp/tests/meta/fidl_llcpp_conformance_test.cmx
new file mode 100644
index 0000000..07de9f0
--- /dev/null
+++ b/src/lib/fidl/llcpp/tests/meta/fidl_llcpp_conformance_test.cmx
@@ -0,0 +1,11 @@
+{
+    "program": {
+        "binary": "test/fidl_llcpp_conformance_test"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.logger.LogSink",
+            "fuchsia.process.Launcher"
+        ]
+    }
+}
diff --git a/tools/fidl/gidl/cpp/builder.go b/tools/fidl/gidl/cpp/builder.go
index b683c26..52211bd 100644
--- a/tools/fidl/gidl/cpp/builder.go
+++ b/tools/fidl/gidl/cpp/builder.go
@@ -5,10 +5,13 @@
 package cpp
 
 import (
+	"bytes"
+	"encoding/hex"
 	"fmt"
 	"strconv"
 	"strings"
 
+	fidlcommon "fidl/compiler/backend/common"
 	fidlir "fidl/compiler/backend/types"
 	gidlir "gidl/ir"
 	gidlmixer "gidl/mixer"
@@ -62,8 +65,24 @@
 	case float64:
 		return fmt.Sprintf("%g", value)
 	case string:
-		// TODO(fxb/39686) Consider Go/C++ escape sequence differences
-		return strconv.Quote(value)
+		if fidlcommon.PrintableASCII(value) {
+			return strconv.Quote(value)
+		}
+		var (
+			buf    bytes.Buffer
+			src    = []byte(value)
+			dstLen = hex.EncodedLen(len(src))
+			dst    = make([]byte, dstLen)
+		)
+		hex.Encode(dst, src)
+		buf.WriteRune('"')
+		for i := 0; i < dstLen; i += 2 {
+			buf.WriteString("\\x")
+			buf.WriteByte(dst[i])
+			buf.WriteByte(dst[i+1])
+		}
+		buf.WriteRune('"')
+		return buf.String()
 	case gidlir.Record:
 		return b.visitRecord(value, decl.(gidlmixer.RecordDeclaration))
 	case []interface{}:
diff --git a/tools/fidl/gidl/rust/common.go b/tools/fidl/gidl/rust/common.go
index c019720..51ec6b8 100644
--- a/tools/fidl/gidl/rust/common.go
+++ b/tools/fidl/gidl/rust/common.go
@@ -17,15 +17,6 @@
 	gidlmixer "gidl/mixer"
 )
 
-func isPrintableASCII(s string) bool {
-	for _, r := range s {
-		if r < 0x20 || r > 0x7e {
-			return false
-		}
-	}
-	return true
-}
-
 func escapeStr(value string) string {
 	var (
 		buf    bytes.Buffer
@@ -60,7 +51,7 @@
 		}
 	case string:
 		var expr string
-		if isPrintableASCII(value) {
+		if fidlcommon.PrintableASCII(value) {
 			expr = fmt.Sprintf("String::from(%q)", value)
 		} else {
 			expr = fmt.Sprintf("std::str::from_utf8(b\"%s\").unwrap().to_string()", escapeStr(value))