blob: 2105864fdc7d737de88bc5b8ff97d8b15c44e3e3 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package golang
import (
"bytes"
"fmt"
"go/format"
"io"
"os"
"strconv"
"strings"
"text/template"
"go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/ir"
"go.fuchsia.dev/fuchsia/tools/fidl/gidl/lib/mixer"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
// withGoFmt wraps a template that produces Go source code, and formats the
// execution result using go/format.
type withGoFmt struct {
template *template.Template
}
func (w withGoFmt) Execute(wr io.Writer, data interface{}) error {
var b bytes.Buffer
if err := w.template.Execute(&b, data); err != nil {
return err
}
formatted, err := format.Source(b.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "gofmt failed: %s\n", err)
_, err = wr.Write(b.Bytes())
return err
}
_, err = wr.Write(formatted)
return err
}
func buildBytes(bytes []byte) string {
var builder strings.Builder
builder.WriteString("[]byte{\n")
for i, b := range bytes {
builder.WriteString(fmt.Sprintf("0x%02x,", b))
if i%8 == 7 {
builder.WriteString("\n")
}
}
builder.WriteString("}")
return builder.String()
}
func buildHandleDefs(defs []ir.HandleDef) string {
if len(defs) == 0 {
return ""
}
var builder strings.Builder
builder.WriteString("[]handleDef{\n")
for i, d := range defs {
var subtype string
rights := d.Rights
switch d.Subtype {
case fidlgen.HandleSubtypeChannel:
subtype = "zx.ObjTypeChannel"
// Always use real rights instead of a "same rights" placeholder.
if rights == fidlgen.HandleRightsSameRights {
r, ok := ir.HandleRightsByName("channel_default")
if !ok {
panic("channel_default should be a supported rights name")
}
rights = r
}
case fidlgen.HandleSubtypeEvent:
subtype = "zx.ObjTypeEvent"
// Always use real rights instead of a "same rights" placeholder.
if rights == fidlgen.HandleRightsSameRights {
r, ok := ir.HandleRightsByName("event_default")
if !ok {
panic("event_default should be a supported rights name")
}
rights = r
}
default:
panic(fmt.Sprintf("unsupported handle subtype: %s", d.Subtype))
}
// Write indices corresponding to the .gidl file handle_defs block.
builder.WriteString(fmt.Sprintf(`
// #%d:
{
subtype: %s,
rights: %d,
},
`, i, subtype, rights))
}
builder.WriteString("}")
return builder.String()
}
func buildHandleInfos(handles []ir.Handle) string {
if len(handles) == 0 {
return "nil"
}
var builder strings.Builder
builder.WriteString("[]zx.HandleInfo{")
for _, handle := range handles {
builder.WriteString(fmt.Sprintf(`
{Handle: handles[%d], Type: handleDefs[%d].subtype, Rights: handleDefs[%d].rights},`, handle, handle, handle))
}
builder.WriteString("\n}")
return builder.String()
}
func buildHandleDispositions(handleDispositions []ir.HandleDisposition) string {
if len(handleDispositions) == 0 {
return "nil"
}
var builder strings.Builder
builder.WriteString("[]zx.HandleDisposition{")
for _, handleDisposition := range handleDispositions {
builder.WriteString(fmt.Sprintf(`
{
Operation: zx.HandleOpMove,
Handle: handles[%d],
Type: %d,
Rights: %d,
Result: zx.ErrOk,
},`,
handleDisposition.Handle, handleDisposition.Type, handleDisposition.Rights))
}
builder.WriteString("\n}")
return builder.String()
}
func buildUnknownData(data ir.UnknownData) string {
return fmt.Sprintf(
"fidl.UnknownData{\nBytes: %s, \nHandles: %s,\n}",
buildBytes(data.Bytes),
buildHandleInfos(data.Handles))
}
func buildUnknownDataMap(fields []ir.Field) string {
if len(fields) == 0 {
return "nil"
}
var builder strings.Builder
builder.WriteString("map[uint64]fidl.UnknownData{\n")
for _, field := range fields {
builder.WriteString(fmt.Sprintf(
"%d: %s,",
field.Key.UnknownOrdinal,
buildUnknownData(field.Value.(ir.UnknownData))))
}
builder.WriteString("}")
return builder.String()
}
func visit(value ir.Value, decl mixer.Declaration) string {
switch value := value.(type) {
case bool, int64, uint64, float64:
switch decl := decl.(type) {
case mixer.PrimitiveDeclaration:
return fmt.Sprintf("%#v", value)
case *mixer.BitsDecl, *mixer.EnumDecl:
return fmt.Sprintf("%s(%d)", typeLiteral(decl), value)
}
case ir.RawFloat:
switch decl.(*mixer.FloatDecl).Subtype() {
case fidlgen.Float32:
return fmt.Sprintf("math.Float32frombits(%#b)", value)
case fidlgen.Float64:
return fmt.Sprintf("math.Float64frombits(%#b)", value)
}
case string:
if decl.IsNullable() {
// Taking an address of a string literal is not allowed, so instead
// we create a slice and get the address of its first element.
return fmt.Sprintf("&[]string{%q}[0]", value)
}
return strconv.Quote(value)
case ir.Handle:
rawHandle := fmt.Sprintf("handles[%d]", value)
switch decl := decl.(type) {
case *mixer.ClientEndDecl:
return fmt.Sprintf("%s{Channel: zx.Channel(%s)}", endpointDeclName(decl), rawHandle)
case *mixer.ServerEndDecl:
return fmt.Sprintf("%s{Channel: zx.Channel(%s)}", endpointDeclName(decl), rawHandle)
case *mixer.HandleDecl:
switch decl.Subtype() {
case fidlgen.HandleSubtypeNone:
return rawHandle
case fidlgen.HandleSubtypeChannel:
return fmt.Sprintf("zx.Channel(%s)", rawHandle)
case fidlgen.HandleSubtypeEvent:
return fmt.Sprintf("zx.Event(%s)", rawHandle)
default:
panic(fmt.Sprintf("Handle subtype not supported %s", decl.Subtype()))
}
}
case ir.Record:
if decl, ok := decl.(mixer.RecordDeclaration); ok {
return onRecord(value, decl)
}
case []ir.Value:
if decl, ok := decl.(mixer.ListDeclaration); ok {
return onList(value, decl)
}
case nil:
if _, ok := decl.(*mixer.HandleDecl); ok {
return "zx.HandleInvalid"
}
if !decl.IsNullable() {
panic(fmt.Sprintf("got nil for non-nullable type: %T", decl))
}
return "nil"
}
panic(fmt.Sprintf("not implemented: %T", value))
}
func onRecord(value ir.Record, decl mixer.RecordDeclaration) string {
var fields []string
if decl, ok := decl.(*mixer.UnionDecl); ok && len(value.Fields) >= 1 {
field := value.Fields[0]
fullName := declName(decl)
var tagValue string
if field.Key.IsUnknown() {
tagValue = fmt.Sprintf("%d", field.Key.UnknownOrdinal)
} else {
fieldName := fidlgen.ToUpperCamelCase(field.Key.Name)
tagValue = fmt.Sprintf("%s%s", fullName, fieldName)
}
parts := strings.Split(string(decl.Name()), "/")
unqualifiedName := fidlgen.ToLowerCamelCase(parts[len(parts)-1])
fields = append(fields,
fmt.Sprintf("I_%sTag: %s", unqualifiedName, tagValue))
}
_, isTable := decl.(*mixer.TableDecl)
var unknownTableFields []ir.Field
for _, field := range value.Fields {
if field.Key.IsUnknown() {
if isTable {
unknownTableFields = append(unknownTableFields, field)
} else {
fields = append(fields,
fmt.Sprintf("I_unknownData: %s", buildUnknownData(field.Value.(ir.UnknownData))))
}
continue
}
fieldName := fidlgen.ToUpperCamelCase(field.Key.Name)
fieldRhs := visit(field.Value, decl.Field(field.Key.Name))
fields = append(fields, fmt.Sprintf("%s: %s", fieldName, fieldRhs))
if isTable && field.Value != nil {
fields = append(fields, fmt.Sprintf("%sPresent: true", fieldName))
}
}
if len(unknownTableFields) > 0 {
fields = append(fields,
fmt.Sprintf("I_unknownData: %s", buildUnknownDataMap(unknownTableFields)))
}
if len(fields) == 0 {
return fmt.Sprintf("%s{}", typeLiteral(decl))
}
// Insert newlines so that gofmt can produce good results.
return fmt.Sprintf("%s{\n%s,\n}", typeLiteral(decl), strings.Join(fields, ",\n"))
}
func onList(value []ir.Value, decl mixer.ListDeclaration) string {
elemDecl := decl.Elem()
var elements []string
for _, item := range value {
elements = append(elements, visit(item, elemDecl))
}
if len(elements) == 0 {
return fmt.Sprintf("%s{}", typeLiteral(decl))
}
// Insert newlines so that gofmt can produce good results.
return fmt.Sprintf("%s{\n%s,\n}", typeLiteral(decl), strings.Join(elements, ",\n"))
}
func typeName(decl mixer.Declaration) string {
return typeNameHelper(decl, "*")
}
func typeLiteral(decl mixer.Declaration) string {
return typeNameHelper(decl, "&")
}
func typeNameHelper(decl mixer.Declaration, pointerPrefix string) string {
if !decl.IsNullable() {
pointerPrefix = ""
}
switch decl := decl.(type) {
case mixer.PrimitiveDeclaration:
return string(decl.Subtype())
case mixer.NamedDeclaration:
return pointerPrefix + declName(decl)
case *mixer.StringDecl:
return pointerPrefix + "string"
case *mixer.ArrayDecl:
return fmt.Sprintf("[%d]%s", decl.Size(), typeName(decl.Elem()))
case *mixer.VectorDecl:
return fmt.Sprintf("%s[]%s", pointerPrefix, typeName(decl.Elem()))
case *mixer.HandleDecl:
switch decl.Subtype() {
case fidlgen.HandleSubtypeNone:
return "zx.Handle"
case fidlgen.HandleSubtypeChannel:
return "zx.Channel"
case fidlgen.HandleSubtypeEvent:
return "zx.Event"
default:
panic(fmt.Sprintf("Handle subtype not supported %s", decl.Subtype()))
}
default:
panic(fmt.Sprintf("unhandled case %T", decl))
}
}
func declName(decl mixer.NamedDeclaration) string {
return identifierName(decl.Name())
}
func endpointDeclName(decl mixer.EndpointDeclaration) string {
switch decl.(type) {
case *mixer.ClientEndDecl:
return fmt.Sprintf("%sWithCtxInterface", identifierName(decl.ProtocolName()))
case *mixer.ServerEndDecl:
return fmt.Sprintf("%sWithCtxInterfaceRequest", identifierName(decl.ProtocolName()))
default:
panic(fmt.Sprintf("unhandled case %T", decl))
}
}
// TODO(https://fxbug.dev/42115264): Move into a common library outside GIDL.
func identifierName(qualifiedName string) string {
parts := strings.Split(qualifiedName, "/")
library_parts := strings.Split(strings.ToLower(parts[0]), ".")
return strings.Join([]string{library_parts[len(library_parts)-1],
fidlgen.ToUpperCamelCase(parts[1])}, ".")
}
// Go errors are defined in third_party/go/src/syscall/zx/fidl/errors.go.
var goErrorCodeNames = map[ir.ErrorCode]string{
ir.EnvelopeBytesExceedMessageLength: "ErrPayloadTooSmall",
ir.EnvelopeHandlesExceedMessageLength: "ErrTooManyHandles",
ir.ExceededMaxOutOfLineDepth: "ErrExceededMaxOutOfLineDepth",
ir.IncorrectHandleType: "ErrIncorrectHandleType",
ir.InvalidBoolean: "ErrInvalidBoolValue",
ir.InvalidEmptyStruct: "ErrInvalidEmptyStruct",
ir.InvalidInlineBitInEnvelope: "ErrInvalidInlineBitValueInEnvelope",
ir.InvalidInlineMarkerInEnvelope: "ErrBadInlineIndicatorEncoding",
ir.InvalidNumBytesInEnvelope: "ErrInvalidNumBytesInEnvelope",
ir.InvalidNumHandlesInEnvelope: "ErrInvalidNumHandlesInEnvelope",
ir.InvalidPaddingByte: "ErrNonZeroPadding",
ir.InvalidPresenceIndicator: "ErrBadRefEncoding",
ir.InvalidHandlePresenceIndicator: "ErrBadHandleEncoding",
ir.MissingRequiredHandleRights: "ErrMissingRequiredHandleRights",
ir.NonEmptyStringWithNullBody: "ErrUnexpectedNullRef",
ir.NonEmptyVectorWithNullBody: "ErrUnexpectedNullRef",
ir.NonNullableTypeWithNullValue: "ErrUnexpectedNullRef",
ir.NonResourceUnknownHandles: "ErrValueTypeHandles",
ir.StrictBitsUnknownBit: "ErrInvalidBitsValue",
ir.StrictEnumUnknownValue: "ErrInvalidEnumValue",
ir.StrictUnionUnknownField: "ErrInvalidXUnionTag",
ir.StringCountExceeds32BitLimit: "ErrStringTooLong",
ir.StringNotUtf8: "ErrStringNotUTF8",
ir.StringTooLong: "ErrStringTooLong",
ir.TableCountExceeds32BitLimit: "ErrUnexpectedOrdinal",
ir.TooFewBytes: "ErrPayloadTooSmall",
ir.TooFewBytesInPrimaryObject: "ErrPayloadTooSmall",
ir.TooFewHandles: "ErrNotEnoughHandles",
ir.TooManyBytesInMessage: "ErrTooManyBytesInMessage",
ir.TooManyHandlesInMessage: "ErrTooManyHandles",
ir.UnionFieldNotSet: "ErrInvalidXUnionTag",
ir.CountExceedsLimit: "ErrVectorTooLong",
ir.UnexpectedOrdinal: "ErrUnexpectedOrdinal",
ir.VectorCountExceeds32BitLimit: "ErrVectorTooLong",
}
func goErrorCode(code ir.ErrorCode) (string, error) {
if str, ok := goErrorCodeNames[code]; ok {
return fmt.Sprintf("fidl.%s", str), nil
}
return "", fmt.Errorf("no go error string defined for error code %s", code)
}