| // 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) |
| } |