| // Copyright 2018 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 fidlgen |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io" |
| "math" |
| "os" |
| "reflect" |
| "strconv" |
| "strings" |
| ) |
| |
| /* |
| This file contains types which describe FIDL protocols. |
| |
| These types are intended to be directly deserialized from the FIDL protocol |
| JSON representation. The types are then passed directly to language-specific |
| generators which produce source code. |
| |
| Note that these are different from a naive AST-based representation of |
| FIDL text. Before being transformed into JSON, FIDL sources are preprocessed |
| to generate metadata required by all of the backends, such as the size of |
| types. Importantly, this removes the need for language-specific backends to |
| implement field, name, or type resolution and analysis. |
| */ |
| |
| // ReadJSONIr reads a JSON IR file. |
| func ReadJSONIr(filename string) (Root, error) { |
| f, err := os.Open(filename) |
| if err != nil { |
| return Root{}, fmt.Errorf("Error reading from %s: %w", filename, err) |
| } |
| return DecodeJSONIr(f) |
| } |
| |
| // DecodeJSONIr reads the JSON content from a reader. |
| func DecodeJSONIr(r io.Reader) (Root, error) { |
| d := json.NewDecoder(r) |
| var root Root |
| if err := d.Decode(&root); err != nil { |
| return Root{}, fmt.Errorf("Error parsing JSON IR: %w", err) |
| } |
| return root, nil |
| } |
| |
| // ReadJSONIrContent reads JSON IR content. |
| func ReadJSONIrContent(b []byte) (Root, error) { |
| return DecodeJSONIr(bytes.NewReader(b)) |
| } |
| |
| type Identifier string |
| |
| // A LibraryIdentifier identifies a FIDL library, from the library declaration |
| // at the start of a FIDL file. |
| type LibraryIdentifier []Identifier |
| |
| // A CompoundIdentifier identifies a particular declaration in a library or |
| // member in a declaration. |
| type CompoundIdentifier struct { |
| // Library the declaration is in. |
| Library LibraryIdentifier |
| // Name of the declaration. |
| Name Identifier |
| // Member of the declaration. If set to empty string, this |
| // CompoundIdentifier refers to the declaration rather than a member. |
| Member Identifier |
| } |
| |
| // Experiment lists the name of an experiment active on this IR. |
| type Experiment string |
| |
| // This is a direct copy of the fidlc experiment flag strings, found at |
| // ///tools/fidl/fidlc/lib/experimental_flags.cc. That list should be considered |
| // the source of truth for this mirror. |
| const ( |
| ExperimentAllowNewTypes Experiment = "allow_new_types" |
| ExperimentOutputIndexJSON Experiment = "output_index_json" |
| ExperimentUnknownInteractions Experiment = "unknown_interactions" |
| ) |
| |
| // Experiments is a list of active experiments for this IR. |
| type Experiments []Experiment |
| |
| // Contains checks if a given experiment string is included in the list. |
| func (exs Experiments) Contains(needle Experiment) bool { |
| for _, ex := range exs { |
| if needle == ex { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // An EncodedLibraryIdentifier is a LibraryIdentifier encoded as a string, |
| // suitable for use in map keys. |
| type EncodedLibraryIdentifier string |
| |
| // An EncodedCompoundIdentifier is a CompoundIdentifier encoded as a string, |
| // suitable for use in map keys. |
| type EncodedCompoundIdentifier string |
| |
| // Encode formats a LibraryIdentifier as a string by joining the identifier |
| // components with ".", e.g. "my.fidl.library". |
| func (li LibraryIdentifier) Encode() EncodedLibraryIdentifier { |
| ss := make([]string, len(li)) |
| for i, s := range li { |
| ss[i] = string(s) |
| } |
| return EncodedLibraryIdentifier(strings.Join(ss, ".")) |
| } |
| |
| // EncodeDecl encodes the fully-qualified declaration portion of the |
| // CompoundIdentifier. |
| // |
| // Encoded form consists of the encoded form of the library identifier, followed |
| // by a slash, then the name of the declaration. If the CompoundIdentifier does |
| // not have a Member, this will be the same as Encode. Example: |
| // "my.fidl.library/MyProtocol". |
| func (ci CompoundIdentifier) EncodeDecl() EncodedCompoundIdentifier { |
| return EncodedCompoundIdentifier(string(ci.Library.Encode()) + "/" + string(ci.Name)) |
| } |
| |
| // Encode encodes the fully-qualified declaration or member identified by this |
| // CompoundIdentifier. |
| // |
| // Encoded form consists of the encoded library identifier, then the declaration |
| // name. If a member is specified, it will come after the declaration name, |
| // separated by a dot. Example: |
| // - With no Member: "my.fidl.library/MyProtocol" |
| // - With Member: "my.fidl.library/MyProtocol.SomeMethod" |
| func (ci CompoundIdentifier) Encode() EncodedCompoundIdentifier { |
| if ci.Member != "" { |
| return EncodedCompoundIdentifier(fmt.Sprintf("%s.%s", ci.EncodeDecl(), ci.Member)) |
| } |
| return ci.EncodeDecl() |
| } |
| |
| // Parts splits the library identifier back into component parts. |
| func (eli EncodedLibraryIdentifier) Parts() []string { |
| return strings.Split(string(eli), ".") |
| } |
| |
| // Parse decodes an EncodedLibraryIdentifier back into a LibraryIdentifier. |
| func (eli EncodedLibraryIdentifier) Parse() LibraryIdentifier { |
| parts := eli.Parts() |
| idents := make([]Identifier, len(parts)) |
| for i, part := range parts { |
| idents[i] = Identifier(part) |
| } |
| return LibraryIdentifier(idents) |
| } |
| |
| // PartsReversed splits the library identifier back into component parts and |
| // returns them in reverse order. |
| func (eli EncodedLibraryIdentifier) PartsReversed() []string { |
| parts := eli.Parts() |
| partsReversed := make([]string, len(parts)) |
| for i, part := range parts { |
| partsReversed[len(parts)-i-1] = string(part) |
| } |
| |
| return partsReversed |
| } |
| |
| // Parts splits an EncodedCompoundIdentifier into an optional library name and |
| // declaration or member id. |
| // |
| // This splits off the library name, but does not check whether the referenced |
| // member is a delaration or member of a declaration. |
| func (eci EncodedCompoundIdentifier) Parts() []string { |
| return strings.SplitN(string(eci), "/", 2) |
| } |
| |
| // LibraryName retrieves the library name from an EncodedCompoundIdentifier. |
| func (eci EncodedCompoundIdentifier) LibraryName() EncodedLibraryIdentifier { |
| raw_library := "" |
| if parts := eci.Parts(); len(parts) == 2 { |
| raw_library = parts[0] |
| } |
| return EncodedLibraryIdentifier(raw_library) |
| } |
| |
| // DeclName retrieves the fully-qualified declaration name from an |
| // EncodedCompoundIdentifier. This operation is idempotent. |
| func (eci EncodedCompoundIdentifier) DeclName() EncodedCompoundIdentifier { |
| ci := eci.Parse() |
| parts := []string{} |
| for _, l := range ci.Library { |
| parts = append(parts, string(l)) |
| } |
| return EncodedCompoundIdentifier(fmt.Sprintf("%s/%s", |
| strings.Join(parts, "."), ci.Name)) |
| } |
| |
| // IsBuiltIn gives whether the identifier corresponds to a built-in type. |
| func (eci EncodedCompoundIdentifier) IsBuiltIn() bool { |
| return eci.LibraryName() == "" |
| } |
| |
| // Parse converts an EncodedCompoundIdentifier back into a CompoundIdentifier. |
| func (eci EncodedCompoundIdentifier) Parse() CompoundIdentifier { |
| parts := eci.Parts() |
| raw_library := "" |
| raw_name := parts[0] |
| if len(parts) == 2 { |
| raw_library = parts[0] |
| raw_name = parts[1] |
| } |
| library := EncodedLibraryIdentifier(raw_library).Parse() |
| name_parts := strings.SplitN(raw_name, ".", 2) |
| name := Identifier(name_parts[0]) |
| member := Identifier("") |
| if len(name_parts) == 2 { |
| member = Identifier(name_parts[1]) |
| } |
| return CompoundIdentifier{library, name, member} |
| } |
| |
| type PrimitiveSubtype string |
| |
| const ( |
| Bool PrimitiveSubtype = "bool" |
| Int8 PrimitiveSubtype = "int8" |
| Int16 PrimitiveSubtype = "int16" |
| Int32 PrimitiveSubtype = "int32" |
| Int64 PrimitiveSubtype = "int64" |
| Uint8 PrimitiveSubtype = "uint8" |
| Uint16 PrimitiveSubtype = "uint16" |
| Uint32 PrimitiveSubtype = "uint32" |
| Uint64 PrimitiveSubtype = "uint64" |
| Float32 PrimitiveSubtype = "float32" |
| Float64 PrimitiveSubtype = "float64" |
| ZxExperimentalUchar PrimitiveSubtype = "uchar" |
| ZxExperimentalUsize64 PrimitiveSubtype = "usize64" |
| ZxExperimentalUintptr64 PrimitiveSubtype = "uintptr64" |
| ) |
| |
| var unsignedSubtypes = map[PrimitiveSubtype]struct{}{ |
| Uint8: {}, |
| Uint16: {}, |
| Uint32: {}, |
| Uint64: {}, |
| } |
| |
| var numberOfBits = map[PrimitiveSubtype]int{ |
| Bool: 8, |
| Int8: 8, |
| Int16: 16, |
| Int32: 32, |
| Int64: 64, |
| Uint8: 8, |
| Uint16: 16, |
| Uint32: 32, |
| Uint64: 64, |
| Float32: 32, |
| Float64: 64, |
| } |
| |
| // IsSigned indicates whether this subtype represents a signed number such as |
| // `int16`, or `float32`. |
| func (typ PrimitiveSubtype) IsSigned() bool { |
| return !typ.IsUnsigned() |
| } |
| |
| // IsUnsigned indicates whether this subtype represents an unsigned number such |
| // as `uint16`. |
| func (typ PrimitiveSubtype) IsUnsigned() bool { |
| _, ok := unsignedSubtypes[typ] |
| return ok |
| } |
| |
| // IsFloat indicates whether this subtype represents a floating-point number. |
| func (typ PrimitiveSubtype) IsFloat() bool { |
| return typ == Float32 || typ == Float64 |
| } |
| |
| // NumberOfBits returns the number of bits used to represent this primitive in FIDL. |
| func (typ PrimitiveSubtype) NumberOfBits() int { |
| return numberOfBits[typ] |
| } |
| |
| // NumberOfBytes returns the number of bytes used to represent this primitive in FIDL. |
| func (typ PrimitiveSubtype) NumberOfBytes() int { |
| return numberOfBits[typ] / 8 |
| } |
| |
| type InternalSubtype string |
| |
| const ( |
| FrameworkErr InternalSubtype = "framework_error" |
| ) |
| |
| type HandleSubtype string |
| |
| const ( |
| HandleSubtypeNone HandleSubtype = "handle" |
| HandleSubtypeBti HandleSubtype = "bti" |
| HandleSubtypeChannel HandleSubtype = "channel" |
| HandleSubtypeClock HandleSubtype = "clock" |
| HandleSubtypeDebugLog HandleSubtype = "debuglog" |
| HandleSubtypeEvent HandleSubtype = "event" |
| HandleSubtypeEventpair HandleSubtype = "eventpair" |
| HandleSubtypeException HandleSubtype = "exception" |
| HandleSubtypeFifo HandleSubtype = "fifo" |
| HandleSubtypeGuest HandleSubtype = "guest" |
| HandleSubtypeInterrupt HandleSubtype = "interrupt" |
| HandleSubtypeIommu HandleSubtype = "iommu" |
| HandleSubtypeJob HandleSubtype = "job" |
| HandleSubtypeMsi HandleSubtype = "msi" |
| HandleSubtypePager HandleSubtype = "pager" |
| HandleSubtypePciDevice HandleSubtype = "pcidevice" |
| HandleSubtypePmt HandleSubtype = "pmt" |
| HandleSubtypePort HandleSubtype = "port" |
| HandleSubtypeProcess HandleSubtype = "process" |
| HandleSubtypeProfile HandleSubtype = "profile" |
| HandleSubtypeResource HandleSubtype = "resource" |
| HandleSubtypeSocket HandleSubtype = "socket" |
| HandleSubtypeStream HandleSubtype = "stream" |
| HandleSubtypeSuspendToken HandleSubtype = "suspendtoken" |
| HandleSubtypeThread HandleSubtype = "thread" |
| HandleSubtypeTimer HandleSubtype = "timer" |
| HandleSubtypeVcpu HandleSubtype = "vcpu" |
| HandleSubtypeVmar HandleSubtype = "vmar" |
| HandleSubtypeVmo HandleSubtype = "vmo" |
| ) |
| |
| // TODO(https://fxbug.dev/42143256): Remove, source of truth is library zx. |
| // |
| // One complication is that GIDL parses nice handle subtypes in its grammar, |
| // e.g. `#0 = event(rights: execute + write )`. And some GIDL backends care |
| // about the object type. This means that we need to duplicate this mapping :/ |
| // It would be cleaner to limit this to GIDL and GIDL backends, rather than |
| // offer that in the general purpose lib/declDepNode |
| type ObjectType uint32 |
| |
| const ( |
| ObjectTypeNone = ObjectType(iota) |
| ObjectTypeProcess |
| ObjectTypeThread |
| ObjectTypeVmo |
| ObjectTypeChannel |
| ObjectTypeEvent |
| ObjectTypePort |
| _ // 7 |
| _ // 8 |
| ObjectTypeInterrupt |
| _ // 10 |
| ObjectTypePciDevice |
| ObjectTypeDebugLog |
| _ // 13 |
| ObjectTypeSocket |
| ObjectTypeResource |
| ObjectTypeEventPair |
| ObjectTypeJob |
| ObjectTypeVmar |
| ObjectTypeFifo |
| ObjectTypeGuest |
| ObjectTypeVcpu |
| ObjectTypeTimer |
| ObjectTypeIommu |
| ObjectTypeBti |
| ObjectTypeProfile |
| ObjectTypePmt |
| ObjectTypeSuspendToken |
| ObjectTypePager |
| ObjectTypeException |
| ObjectTypeClock |
| ObjectTypeStream |
| ObjectTypeMsi |
| ) |
| |
| func ObjectTypeFromHandleSubtype(val HandleSubtype) ObjectType { |
| switch val { |
| case HandleSubtypeBti: |
| return ObjectTypeBti |
| case HandleSubtypeChannel: |
| return ObjectTypeChannel |
| case HandleSubtypeClock: |
| return ObjectTypeClock |
| case HandleSubtypeDebugLog: |
| return ObjectTypeDebugLog |
| case HandleSubtypeEvent: |
| return ObjectTypeEvent |
| case HandleSubtypeEventpair: |
| return ObjectTypeEventPair |
| case HandleSubtypeException: |
| return ObjectTypeException |
| case HandleSubtypeFifo: |
| return ObjectTypeFifo |
| case HandleSubtypeGuest: |
| return ObjectTypeGuest |
| case HandleSubtypeInterrupt: |
| return ObjectTypeInterrupt |
| case HandleSubtypeIommu: |
| return ObjectTypeIommu |
| case HandleSubtypeJob: |
| return ObjectTypeJob |
| case HandleSubtypeMsi: |
| return ObjectTypeMsi |
| case HandleSubtypePager: |
| return ObjectTypePager |
| case HandleSubtypePciDevice: |
| return ObjectTypePciDevice |
| case HandleSubtypePmt: |
| return ObjectTypePmt |
| case HandleSubtypePort: |
| return ObjectTypePort |
| case HandleSubtypeProcess: |
| return ObjectTypeProcess |
| case HandleSubtypeProfile: |
| return ObjectTypeProfile |
| case HandleSubtypeResource: |
| return ObjectTypeResource |
| case HandleSubtypeSocket: |
| return ObjectTypeSocket |
| case HandleSubtypeStream: |
| return ObjectTypeStream |
| case HandleSubtypeSuspendToken: |
| return ObjectTypeSuspendToken |
| case HandleSubtypeThread: |
| return ObjectTypeThread |
| case HandleSubtypeTimer: |
| return ObjectTypeTimer |
| case HandleSubtypeVcpu: |
| return ObjectTypeVcpu |
| case HandleSubtypeVmar: |
| return ObjectTypeVmar |
| case HandleSubtypeVmo: |
| return ObjectTypeVmo |
| default: |
| return ObjectTypeNone |
| } |
| } |
| |
| type HandleRights uint32 |
| |
| const ( |
| HandleRightsNone HandleRights = 0 |
| |
| HandleRightsDuplicate HandleRights = 1 << 0 |
| HandleRightsTransfer HandleRights = 1 << 1 |
| HandleRightsRead HandleRights = 1 << 2 |
| HandleRightsWrite HandleRights = 1 << 3 |
| HandleRightsExecute HandleRights = 1 << 4 |
| HandleRightsMap HandleRights = 1 << 5 |
| HandleRightsGetProperty HandleRights = 1 << 6 |
| HandleRightsSetProperty HandleRights = 1 << 7 |
| HandleRightsEnumerate HandleRights = 1 << 8 |
| HandleRightsDestroy HandleRights = 1 << 9 |
| HandleRightsSetPolicy HandleRights = 1 << 10 |
| HandleRightsGetPolicy HandleRights = 1 << 11 |
| HandleRightsSignal HandleRights = 1 << 12 |
| HandleRightsSignalPeer HandleRights = 1 << 13 |
| HandleRightsWait HandleRights = 1 << 14 |
| HandleRightsInspect HandleRights = 1 << 15 |
| HandleRightsManageJob HandleRights = 1 << 16 |
| HandleRightsManageProcess HandleRights = 1 << 17 |
| HandleRightsManageThread HandleRights = 1 << 18 |
| HandleRightsApplyProfile HandleRights = 1 << 19 |
| |
| HandleRightsSameRights HandleRights = 1 << 31 |
| |
| HandleRightsBasic HandleRights = HandleRightsTransfer | HandleRightsDuplicate | HandleRightsWait | HandleRightsInspect |
| HandleRightsChannelDefault HandleRights = HandleRightsTransfer | HandleRightsWait | HandleRightsInspect | HandleRightsRead | HandleRightsWrite | HandleRightsSignal | HandleRightsSignalPeer |
| ) |
| |
| type LiteralKind string |
| |
| const ( |
| StringLiteral LiteralKind = "string" |
| NumericLiteral LiteralKind = "numeric" |
| BoolLiteral LiteralKind = "bool" |
| DefaultLiteral LiteralKind = "default" |
| ) |
| |
| type Literal struct { |
| Kind LiteralKind `json:"kind"` |
| Value string `json:"value"` |
| Expression string `json:"expression"` |
| } |
| |
| type ConstantKind string |
| |
| const ( |
| IdentifierConstant ConstantKind = "identifier" |
| LiteralConstant ConstantKind = "literal" |
| BinaryOperator ConstantKind = "binary_operator" |
| ) |
| |
| type Constant struct { |
| Kind ConstantKind `json:"kind"` |
| Identifier EncodedCompoundIdentifier `json:"identifier,omitempty"` |
| Literal *Literal `json:"literal,omitempty"` |
| Value string `json:"value"` |
| Expression string `json:"expression"` |
| } |
| |
| // Location gives the location of the FIDL declaration in its source `.fidl` |
| // file. |
| type Location struct { |
| Filename string `json:"filename"` |
| Line int `json:"line"` |
| Column int `json:"column"` |
| Length int `json:"length"` |
| } |
| |
| // Compares two fidlgen.Locations lexicographically on filename and then on |
| // the location within the file. |
| func LocationCmp(a, b Location) bool { |
| if cmp := strings.Compare(a.Filename, b.Filename); cmp != 0 { |
| return cmp < 0 |
| } |
| if a.Line != b.Line { |
| return a.Line < b.Line |
| } |
| if a.Column != b.Column { |
| return a.Column < b.Column |
| } |
| return a.Length < b.Length |
| } |
| |
| type TypeKind string |
| |
| const ( |
| ArrayType TypeKind = "array" |
| VectorType TypeKind = "vector" |
| StringType TypeKind = "string" |
| HandleType TypeKind = "handle" |
| RequestType TypeKind = "request" |
| PrimitiveType TypeKind = "primitive" |
| IdentifierType TypeKind = "identifier" |
| InternalType TypeKind = "internal" |
| ZxExperimentalPointerType TypeKind = "experimental_pointer" |
| StringArray TypeKind = "string_array" |
| ) |
| |
| type Boundedness string |
| |
| const ( |
| BoundednessBounded Boundedness = "bounded" |
| BoundednessSemiBounded Boundedness = "semi_bounded" |
| BoundednessUnbounded Boundedness = "unbounded" |
| ) |
| |
| type Type struct { |
| Kind TypeKind |
| ElementType *Type |
| ElementCount *int |
| HandleSubtype HandleSubtype |
| HandleRights HandleRights |
| RequestSubtype EncodedCompoundIdentifier |
| PrimitiveSubtype PrimitiveSubtype |
| Identifier EncodedCompoundIdentifier |
| InternalSubtype InternalSubtype |
| Nullable bool |
| ProtocolTransport string |
| ObjType uint32 |
| ResourceIdentifier string |
| TypeShapeV2 TypeShape |
| PointeeType *Type |
| } |
| |
| // UnmarshalJSON customizes the JSON unmarshalling for Type. |
| func (t *Type) UnmarshalJSON(b []byte) error { |
| var obj map[string]*json.RawMessage |
| err := json.Unmarshal(b, &obj) |
| if err != nil { |
| return err |
| } |
| |
| err = json.Unmarshal(*obj["kind"], &t.Kind) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["type_shape_v2"], &t.TypeShapeV2) |
| if err != nil { |
| return err |
| } |
| |
| switch t.Kind { |
| case ArrayType: |
| t.ElementType = &Type{} |
| err = json.Unmarshal(*obj["element_type"], t.ElementType) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["element_count"], &t.ElementCount) |
| if err != nil { |
| return err |
| } |
| case StringArray: |
| // Treat string_array as just an array of uint8 |
| t.ElementType = &Type{Kind: PrimitiveType, PrimitiveSubtype: Uint8} |
| err = json.Unmarshal(*obj["element_count"], &t.ElementCount) |
| if err != nil { |
| return err |
| } |
| case VectorType: |
| t.ElementType = &Type{} |
| err = json.Unmarshal(*obj["element_type"], t.ElementType) |
| if err != nil { |
| return err |
| } |
| if elementCount, ok := obj["maybe_element_count"]; ok { |
| err = json.Unmarshal(*elementCount, &t.ElementCount) |
| if err != nil { |
| return err |
| } |
| } |
| err = json.Unmarshal(*obj["nullable"], &t.Nullable) |
| if err != nil { |
| return err |
| } |
| case StringType: |
| if elementCount, ok := obj["maybe_element_count"]; ok { |
| err = json.Unmarshal(*elementCount, &t.ElementCount) |
| if err != nil { |
| return err |
| } |
| } |
| err = json.Unmarshal(*obj["nullable"], &t.Nullable) |
| if err != nil { |
| return err |
| } |
| case HandleType: |
| err = json.Unmarshal(*obj["subtype"], &t.HandleSubtype) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["rights"], &t.HandleRights) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["nullable"], &t.Nullable) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["obj_type"], &t.ObjType) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["resource_identifier"], &t.ResourceIdentifier) |
| if err != nil { |
| return err |
| } |
| case RequestType: |
| err = json.Unmarshal(*obj["subtype"], &t.RequestSubtype) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["nullable"], &t.Nullable) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["protocol_transport"], &t.ProtocolTransport) |
| if err != nil { |
| return err |
| } |
| case PrimitiveType: |
| err = json.Unmarshal(*obj["subtype"], &t.PrimitiveSubtype) |
| if err != nil { |
| return err |
| } |
| case IdentifierType: |
| err = json.Unmarshal(*obj["identifier"], &t.Identifier) |
| if err != nil { |
| return err |
| } |
| err = json.Unmarshal(*obj["nullable"], &t.Nullable) |
| if err != nil { |
| return err |
| } |
| if protocolTransport, ok := obj["protocol_transport"]; ok { |
| err = json.Unmarshal(*protocolTransport, &t.ProtocolTransport) |
| if err != nil { |
| return err |
| } |
| } |
| case InternalType: |
| err = json.Unmarshal(*obj["subtype"], &t.InternalSubtype) |
| if err != nil { |
| return err |
| } |
| case ZxExperimentalPointerType: |
| t.PointeeType = &Type{} |
| err = json.Unmarshal(*obj["pointee_type"], t.PointeeType) |
| if err != nil { |
| return err |
| } |
| default: |
| return fmt.Errorf("Unknown type kind: %s", t.Kind) |
| } |
| |
| return nil |
| } |
| |
| // MarshalJSON customizes the JSON marshalling for Type. |
| func (t *Type) MarshalJSON() ([]byte, error) { |
| obj := map[string]interface{}{ |
| "kind": t.Kind, |
| "type_shape_v2": t.TypeShapeV2, |
| } |
| switch t.Kind { |
| case ArrayType: |
| obj["element_type"] = t.ElementType |
| obj["element_count"] = t.ElementCount |
| case StringArray: |
| obj["element_count"] = t.ElementCount |
| case VectorType: |
| obj["element_type"] = t.ElementType |
| if t.ElementCount != nil { |
| obj["maybe_element_count"] = t.ElementCount |
| } |
| obj["nullable"] = t.Nullable |
| case StringType: |
| if t.ElementCount != nil { |
| obj["maybe_element_count"] = t.ElementCount |
| } |
| obj["nullable"] = t.Nullable |
| case HandleType: |
| obj["subtype"] = t.HandleSubtype |
| obj["rights"] = t.HandleRights |
| obj["nullable"] = t.Nullable |
| obj["obj_type"] = t.ObjType |
| obj["resource_identifier"] = t.ResourceIdentifier |
| case RequestType: |
| obj["subtype"] = t.RequestSubtype |
| obj["nullable"] = t.Nullable |
| obj["protocol_transport"] = t.ProtocolTransport |
| case PrimitiveType: |
| obj["subtype"] = t.PrimitiveSubtype |
| case IdentifierType: |
| obj["identifier"] = t.Identifier |
| obj["nullable"] = t.Nullable |
| obj["protocol_transport"] = t.ProtocolTransport |
| case InternalType: |
| obj["subtype"] = t.InternalSubtype |
| case ZxExperimentalPointerType: |
| obj["pointee_type"] = t.PointeeType |
| default: |
| return nil, fmt.Errorf("unknown type kind: %#v", t) |
| } |
| return json.Marshal(obj) |
| } |
| |
| // Boundedness calculates whether the type in question is bounded, semi-bounded, |
| // or unbounded. |
| func (t *Type) Boundedness() Boundedness { |
| // All calculations are performed using `TypeShapeV2`, though in principal |
| // both type shapes should be identical with respect to the properties being |
| // checked. |
| ts := t.TypeShapeV2 |
| |
| // MaxUint32 is the sentinel value that the JSON IR uses to represent |
| // "unlimited" in this case. |
| if ts.Depth <= 32 && ts.MaxOutOfLine < math.MaxUint32 { |
| if ts.HasFlexibleEnvelope { |
| return BoundednessSemiBounded |
| } |
| return BoundednessBounded |
| } |
| return BoundednessUnbounded |
| } |
| |
| // MaxEncodeSize returns the maximum encodable size of the type, or nil if the |
| // maximum size is unbounded. Note that this only applies to encode (ie, the |
| // known shape of the type); on decode, a type may be larger than this size if |
| // it contains flexible envelopes (ie, is semi-bounded). |
| func (t *Type) MaxEncodeSize() *int { |
| if t.Boundedness() == BoundednessUnbounded { |
| return nil |
| } |
| |
| // All calculations are performed using `TypeShapeV2`, though in principal |
| // both type shapes should be identical with respect to the properties being |
| // checked. |
| ts := t.TypeShapeV2 |
| mes := ts.InlineSize + ts.MaxOutOfLine |
| return &mes |
| } |
| |
| // MaxDecodeSize returns the maximum decodable size of the type, or nil if the |
| // maximum size is unbounded. |
| func (t *Type) MaxDecodeSize() *int { |
| if t.Boundedness() != BoundednessBounded { |
| return nil |
| } |
| |
| // All calculations are performed using `TypeShapeV2`, though in principal |
| // both type shapes should be identical with respect to the properties being |
| // checked. |
| ts := t.TypeShapeV2 |
| mds := ts.InlineSize + ts.MaxOutOfLine |
| return &mds |
| } |
| |
| type AttributeArg struct { |
| Name Identifier `json:"name"` |
| Value Constant `json:"value"` |
| Type string `json:"type"` |
| } |
| |
| // ValueString returns the attribute arg's value in string form. |
| // TODO(https://fxbug.dev/42161840): Attribute values may only be string literals for now. |
| // Make sure to fix this API once that changes to resolve the constant value |
| // for all constant types. |
| func (el AttributeArg) ValueString() string { |
| return el.Value.Value |
| } |
| |
| type Attribute struct { |
| Name Identifier `json:"name"` |
| Args []AttributeArg `json:"arguments"` |
| } |
| |
| func (el Attribute) LookupArg(name Identifier) (AttributeArg, bool) { |
| for _, a := range el.Args { |
| if a.Name == name { |
| return a, true |
| } |
| } |
| return AttributeArg{}, false |
| } |
| |
| func (el Attribute) LookupArgStandalone() (AttributeArg, bool) { |
| if len(el.Args) != 1 { |
| return AttributeArg{}, false |
| } |
| return el.Args[0], true |
| } |
| |
| func (el Attribute) HasArg(name Identifier) bool { |
| _, ok := el.LookupArg(name) |
| return ok |
| } |
| |
| // Attributes represents a list of attributes. It conveniently implements the |
| // `Annotated` protocol, such that it can be embedded into other node structs |
| // which are annotated. |
| type Attributes struct { |
| Attributes []Attribute `json:"maybe_attributes,omitempty"` |
| } |
| |
| func (el Attributes) LookupAttribute(name Identifier) (Attribute, bool) { |
| for _, a := range el.Attributes { |
| if ToSnakeCase(string(a.Name)) == ToSnakeCase(string(name)) { |
| return a, true |
| } |
| } |
| return Attribute{}, false |
| } |
| |
| func (el Attributes) HasAttribute(name Identifier) bool { |
| _, ok := el.LookupAttribute(name) |
| return ok |
| } |
| |
| func (el Attributes) DocComments() []string { |
| attr, ok := el.LookupAttribute("doc") |
| if !ok { |
| return nil |
| } |
| doc, ok := attr.LookupArgStandalone() |
| docVal := doc.ValueString() |
| if !ok || docVal == "" { |
| return nil |
| } |
| return strings.Split(docVal[0:len(docVal)-1], "\n") |
| } |
| |
| func (el Attributes) Transports() map[string]struct{} { |
| transports := make(map[string]struct{}) |
| attr, ok := el.LookupAttribute("transport") |
| if ok { |
| raw, ok := attr.LookupArgStandalone() |
| if ok && raw.ValueString() != "" { |
| for _, transport := range strings.Split(raw.ValueString(), ",") { |
| transports[strings.TrimSpace(transport)] = struct{}{} |
| } |
| } |
| } |
| // No transport attribute => just Channel |
| if !ok { |
| transports["Channel"] = struct{}{} |
| } |
| return transports |
| } |
| |
| func (el Attributes) OverTransport() string { |
| // No transport attribute => just Channel |
| attr, ok := el.LookupAttribute("transport") |
| if !ok { |
| return "Channel" |
| } |
| raw, ok := attr.LookupArgStandalone() |
| if !ok || raw.ValueString() == "" { |
| return "Channel" |
| } |
| return raw.ValueString() |
| } |
| |
| // BindingsDenylistIncludes returns true if the comma-separated |
| // bindings_denylist attribute includes targetLanguage (meaning the bindings for |
| // targetLanguage should not emit this declaration). |
| func (el Attributes) BindingsDenylistIncludes(targetLanguage string) bool { |
| attr, ok := el.LookupAttribute("bindings_denylist") |
| if !ok { |
| return false |
| } |
| raw, ok := attr.LookupArgStandalone() |
| if ok && raw.ValueString() != "" { |
| for _, language := range strings.Split(raw.ValueString(), ",") { |
| if strings.TrimSpace(language) == targetLanguage { |
| return true |
| } |
| } |
| } |
| return false |
| } |
| |
| // TypeShape represents the shape of the type on the wire. |
| // See JSON IR schema, e.g. fidlc --json-schema |
| type TypeShape struct { |
| InlineSize int `json:"inline_size"` |
| Alignment int `json:"alignment"` |
| Depth int `json:"depth"` |
| MaxHandles int `json:"max_handles"` |
| MaxOutOfLine int `json:"max_out_of_line"` |
| HasPadding bool `json:"has_padding"` |
| HasFlexibleEnvelope bool `json:"has_flexible_envelope"` |
| } |
| |
| // FieldShape represents the shape of the field on the wire. |
| // See JSON IR schema, e.g. fidlc --json-schema |
| type FieldShape struct { |
| Offset int `json:"offset"` |
| Padding int `json:"padding"` |
| } |
| |
| // Element represents a general FIDL element: namely a declaration or one of |
| // its members. |
| type Element interface { |
| GetAttributes() Attributes |
| GetLocation() Location |
| IsDeprecated() bool |
| } |
| |
| var _ = []Element{ |
| (*Const)(nil), |
| (*Bits)(nil), |
| (*BitsMember)(nil), |
| (*Enum)(nil), |
| (*EnumMember)(nil), |
| (*Resource)(nil), |
| (*Service)(nil), |
| (*ServiceMember)(nil), |
| (*Protocol)(nil), |
| (*Method)(nil), |
| (*Struct)(nil), |
| (*StructMember)(nil), |
| (*Table)(nil), |
| (*TableMember)(nil), |
| (*Union)(nil), |
| (*UnionMember)(nil), |
| (*Alias)(nil), |
| (*NewType)(nil), |
| (*Overlay)(nil), |
| (*OverlayMember)(nil), |
| } |
| |
| // Decl represents a FIDL declaration. |
| type Decl interface { |
| Element |
| GetName() EncodedCompoundIdentifier |
| } |
| |
| var _ = []Decl{ |
| (*Const)(nil), |
| (*Bits)(nil), |
| (*Enum)(nil), |
| (*Resource)(nil), |
| (*Service)(nil), |
| (*Protocol)(nil), |
| (*Struct)(nil), |
| (*Table)(nil), |
| (*Union)(nil), |
| (*Alias)(nil), |
| (*NewType)(nil), |
| (*Overlay)(nil), |
| } |
| |
| type decl struct { |
| Attributes |
| Location `json:"location"` |
| Deprecated bool `json:"deprecated"` |
| Name EncodedCompoundIdentifier `json:"name"` |
| } |
| |
| func (d decl) GetAttributes() Attributes { |
| return d.Attributes |
| } |
| |
| func (d decl) GetLocation() Location { |
| return d.Location |
| } |
| |
| func (d decl) IsDeprecated() bool { |
| return d.Deprecated |
| } |
| |
| func (d decl) GetName() EncodedCompoundIdentifier { |
| return d.Name |
| } |
| |
| // Member represents a member of FIDL layout declaration. |
| type Member interface { |
| Element |
| GetName() Identifier |
| } |
| |
| var _ = []Member{ |
| (*BitsMember)(nil), |
| (*EnumMember)(nil), |
| (*ServiceMember)(nil), |
| (*Method)(nil), |
| (*StructMember)(nil), |
| (*TableMember)(nil), |
| (*UnionMember)(nil), |
| } |
| |
| type member struct { |
| Attributes |
| Location `json:"location"` |
| Deprecated bool `json:"deprecated"` |
| Name Identifier `json:"name,omitempty"` |
| } |
| |
| func (m member) GetAttributes() Attributes { |
| return m.Attributes |
| } |
| |
| func (m member) GetLocation() Location { |
| return m.Location |
| } |
| |
| func (m member) IsDeprecated() bool { |
| return m.Deprecated |
| } |
| |
| func (m member) GetName() Identifier { |
| return m.Name |
| } |
| |
| // NamingContext represents the content of the `naming_context` JSON IR field, |
| // which enumerates inr order the names of the parent declarations of some |
| // declaration. Top-level declarations have a list of size 1, with their own |
| // name as the only member. Nested (ie, anonymous) declarations are lists of a |
| // size greater than 1, starting with the outer most ancestor declaration. |
| // |
| // While the `name` and the last string in a `naming_context` are usually |
| // identical, the `name` can be arbitrarily changed using the |
| // `@generated_name()` FIDL annotation, so this is not guaranteed to be the |
| // case. |
| type NamingContext []string |
| |
| // IsAnonymous states whether the described NamingContext indicates anonymous |
| // declaration (ie, not |
| // explicitly named in the source FIDL). |
| func (nc NamingContext) IsAnonymous() bool { |
| return len(nc) > 1 |
| } |
| |
| // Join builds a name string out of the disparate parts of the NamingContext. |
| func (nc NamingContext) Join() string { |
| return strings.Join(nc, "") |
| } |
| |
| // scopedNamingContext stores a NamingContext that also includes the library |
| // from which that naming context was sourced. This is useful for comparing |
| // identical NamingContexts from different libraries for uniqueness. |
| type scopedNamingContext struct { |
| lib EncodedLibraryIdentifier |
| nc NamingContext |
| } |
| |
| // nestedIn returns true if s is nested in (or is the same as) other. |
| func (s scopedNamingContext) nestedIn(other scopedNamingContext) bool { |
| if s.lib != other.lib || len(s.nc) < len(other.nc) { |
| return false |
| } |
| for i, v := range other.nc { |
| if s.nc[i] != v { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // nestedInAny returns true if s is nested in (or is the same as) any of others. |
| func (s scopedNamingContext) nestedInAny(others []scopedNamingContext) bool { |
| for _, other := range others { |
| if s.nestedIn(other) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // LayoutDecl represents data specific to bits/enums/structs/tables/unions. All |
| // layouts are decls, but not all decls are layouts (e.g. protocols). |
| type LayoutDecl interface { |
| Decl |
| GetNamingContext() NamingContext |
| } |
| |
| var _ = []LayoutDecl{ |
| (*Union)(nil), |
| (*Table)(nil), |
| (*Struct)(nil), |
| (*Enum)(nil), |
| } |
| |
| type layoutDecl struct { |
| decl |
| NamingContext NamingContext `json:"naming_context"` |
| } |
| |
| func (l layoutDecl) GetNamingContext() NamingContext { |
| return l.NamingContext |
| } |
| |
| // IsAnonymous states whether this layoutDecl has an anonymous naming context. We |
| // treat inner layouts (i.e. layouts defined within another layout) as |
| // anonymous. All such layouts have a naming context with length greater than |
| // one, since they include at least the top level name followed by one or more |
| // inner names. |
| func (l *layoutDecl) IsAnonymous() bool { |
| return l.NamingContext.IsAnonymous() |
| } |
| |
| // A ResourceableLayoutDecl represents a layout that possesses |
| // "resourceness" (i.e., the ability to contain a resource type), |
| type ResourceableLayoutDecl interface { |
| LayoutDecl |
| GetResourceness() Resourceness |
| } |
| |
| var _ = []ResourceableLayoutDecl{ |
| (*Union)(nil), |
| (*Table)(nil), |
| (*Struct)(nil), |
| } |
| |
| type resourceableLayoutDecl struct { |
| layoutDecl |
| Resourceness `json:"resource"` |
| } |
| |
| func (rl resourceableLayoutDecl) GetResourceness() Resourceness { |
| return rl.Resourceness |
| } |
| |
| // Alias represents the declaration of a FIDL alias. |
| type Alias struct { |
| decl |
| Type Type `json:"type"` |
| MaybeAlias *PartialTypeConstructor `json:"experimental_maybe_from_alias,omitempty"` |
| // TODO(https://fxbug.dev/42158155): Remove PartialTypeConstructor. |
| PartialTypeConstructor PartialTypeConstructor `json:"partial_type_ctor"` |
| } |
| |
| // PartialTypeConstructor represents a FIDL type as it is constructed from |
| // other type arguments. |
| type PartialTypeConstructor struct { |
| Name EncodedCompoundIdentifier `json:"name"` |
| Args []PartialTypeConstructor `json:"args"` |
| Nullable bool `json:"nullable"` |
| MaybeSize *Constant `json:"maybe_size,omitempty"` |
| } |
| |
| // NewType represents the declaration of a FIDL 'new type'. |
| type NewType struct { |
| decl |
| Type Type `json:"type"` |
| Alias *PartialTypeConstructor `json:"experimental_maybe_from_alias,omitempty"` |
| } |
| |
| // Union represents the declaration of a FIDL union. |
| type Union struct { |
| resourceableLayoutDecl |
| IsResult bool `json:"is_result"` |
| Members []UnionMember `json:"members"` |
| Strictness `json:"strict"` |
| TypeShapeV2 TypeShape `json:"type_shape_v2"` |
| } |
| |
| // UnionMember represents the declaration of a field in a FIDL extensible |
| // union. |
| type UnionMember struct { |
| member |
| Ordinal int `json:"ordinal"` |
| Type Type `json:"type"` |
| MaybeAlias *PartialTypeConstructor `json:"experimental_maybe_from_alias,omitempty"` |
| } |
| |
| // Table represents a declaration of a FIDL table. |
| type Table struct { |
| resourceableLayoutDecl |
| Members []TableMember `json:"members"` |
| Strictness `json:"strict"` |
| TypeShapeV2 TypeShape `json:"type_shape_v2"` |
| } |
| |
| // TableMember represents the declaration of a field in a FIDL table. |
| type TableMember struct { |
| member |
| Type Type `json:"type"` |
| Ordinal int `json:"ordinal"` |
| MaybeAlias *PartialTypeConstructor `json:"experimental_maybe_from_alias,omitempty"` |
| } |
| |
| // Struct represents a declaration of a FIDL struct. |
| type Struct struct { |
| resourceableLayoutDecl |
| IsEmptySuccessStruct bool `json:"is_empty_success_struct"` |
| Members []StructMember `json:"members"` |
| MaxHandles int `json:"max_handles"` |
| TypeShapeV2 TypeShape `json:"type_shape_v2"` |
| } |
| |
| // StructMember represents the declaration of a field in a FIDL struct. |
| type StructMember struct { |
| member |
| Type Type `json:"type"` |
| MaybeDefaultValue *Constant `json:"maybe_default_value,omitempty"` |
| MaybeAlias *PartialTypeConstructor `json:"experimental_maybe_from_alias,omitempty"` |
| FieldShapeV2 FieldShape `json:"field_shape_v2"` |
| } |
| |
| // EmptyStructMember returns a StructMember that's suitable as the sole member |
| // of an empty struct. |
| func EmptyStructMember(name string) StructMember { |
| // Empty structs have a size of 1, so the uint8 struct member returned by this |
| // function can be used to pad the struct to the correct size. |
| |
| return StructMember{ |
| member: member{ |
| Name: Identifier(name), |
| }, |
| Type: Type{ |
| Kind: PrimitiveType, |
| PrimitiveSubtype: Uint8, |
| }, |
| MaybeDefaultValue: &Constant{ |
| Kind: "literal", |
| Identifier: "", |
| Literal: &Literal{ |
| Kind: "numeric", |
| Value: "0", |
| }, |
| }, |
| } |
| } |
| |
| // Overlay represents a declaration of a FIDL overlay. |
| type Overlay struct { |
| resourceableLayoutDecl |
| Members []OverlayMember `json:"members"` |
| Strictness `json:"strict"` |
| TypeShapeV2 TypeShape `json:"type_shape_v2"` |
| } |
| |
| // OverlayMember represents the declaration of a member in a FIDL overlay. |
| type OverlayMember struct { |
| member |
| Ordinal int `json:"ordinal"` |
| Type Type `json:"type"` |
| MaybeAlias *PartialTypeConstructor `json:"experimental_maybe_from_alias,omitempty"` |
| } |
| |
| // Openness of a protocol. Affects whether unknown interaction handlers are generated. Also controls |
| // whether methods are allowed to be flexible, but that is enforced by fidlc, not fidlgen. |
| type Openness string |
| |
| const ( |
| Open Openness = "open" |
| Ajar Openness = "ajar" |
| Closed Openness = "closed" |
| ) |
| |
| func (o Openness) IsClosed() bool { |
| switch o { |
| case Open, Ajar: |
| return false |
| case Closed, "": |
| return true |
| default: |
| panic(fmt.Errorf("invalid openness %s", o)) |
| } |
| } |
| |
| // Protocol represents the declaration of a FIDL protocol. |
| type Protocol struct { |
| decl |
| // Whether the protocol is open. This affects whether the server-side generates handlers for |
| // unknown interactions. |
| Openness Openness `json:"openness,omitempty"` |
| // List of methods that are part of this protocol. |
| Methods []Method `json:"methods"` |
| // List of composed protocols. |
| Composed []decl `json:"composed_protocols"` |
| } |
| |
| // If the protocol is discoverable, gets the discovery name for the protocol, consisting of the |
| // library name and protocol declaration name separated by dots and enclosed in quotes. For example, |
| // "\"my.library.MyProtocol\"". This part of legacy service discovery (pre-RFC-0041). |
| func (d *Protocol) GetProtocolName() string { |
| attr, ok := d.LookupAttribute("discoverable") |
| if !ok { |
| return "" |
| } |
| var name string |
| if arg, ok := attr.LookupArgStandalone(); ok { |
| name = arg.ValueString() |
| } else { |
| // TODO(https://fxbug.dev/42053780): Construct this string in fidlc, not here. |
| ci := d.Name.Parse() |
| var parts []string |
| for _, i := range ci.Library { |
| parts = append(parts, string(i)) |
| } |
| parts = append(parts, string(ci.Name)) |
| name = strings.Join(parts, ".") |
| } |
| return strconv.Quote(name) |
| } |
| |
| // Returns true if this protocol must handle one-way unknown interactions. |
| func (p *Protocol) OneWayUnknownInteractions() bool { |
| return p.Openness == Open || p.Openness == Ajar |
| } |
| |
| // Returns true if this protocol must handle two-way unknown interactions. |
| func (p *Protocol) TwoWayUnknownInteractions() bool { |
| return p.Openness == Open |
| } |
| |
| // Service represents the declaration of a FIDL service. |
| type Service struct { |
| decl |
| Members []ServiceMember `json:"members"` |
| } |
| |
| func (s *Service) GetServiceName() string { |
| ci := s.Name.Parse() |
| var parts []string |
| for _, i := range ci.Library { |
| parts = append(parts, string(i)) |
| } |
| parts = append(parts, string(ci.Name)) |
| return strings.Join(parts, ".") |
| } |
| |
| // ServiceMember represents the declaration of a field in a FIDL service. |
| type ServiceMember struct { |
| member |
| Type Type `json:"type"` |
| } |
| |
| // Method represents the declaration of a FIDL method. |
| type Method struct { |
| member |
| |
| // Computed ordinal to use to identify the method on the wire. |
| Ordinal uint64 `json:"ordinal"` |
| // Whether the method is marked as strict (otherwise flexible). |
| // |
| // While unknown interactions are experimental, a not-set strictness should |
| // be treated as strict. After unknown interactions are released this field |
| // will be made required. For now, use IsStrict() to access the value |
| // correctly. |
| // |
| // TODO(https://fxbug.dev/42169590): make "strict" required once fidlc always provides |
| // it. It's currently gated by unknown_interactions and should not default |
| // to false when not provided. |
| MaybeStrict *bool `json:"strict,omitempty"` |
| // True if the method was composed into this protocol from another protocol |
| // definition. |
| IsComposed bool `json:"is_composed"` |
| // True if this method has a request. This is true for all client-initiated |
| // methods, and false for server-initiated events. There may still be no |
| // request payload, for example "Foo()" has a request but no request |
| // payload. |
| HasRequest bool `json:"has_request"` |
| // The request payload of the method, given in the method arguments. |
| RequestPayload *Type `json:"maybe_request_payload,omitempty"` |
| // True if this method has a response. This is true for two-way methods and |
| // for server-initiated events. There may still be no response payload, for |
| // example "Foo(...) -> ()" or "-> Bar()" have a response but no response |
| // payload. |
| HasResponse bool `json:"has_response"` |
| // The full response payload, as it appears on the wire. If flexible or |
| // error syntax are used, this is the compiler-generated result union. |
| ResponsePayload *Type `json:"maybe_response_payload,omitempty"` |
| // Whether the method uses the "error" syntax. If true, ResponsePayload will |
| // be the result union, and ValueType and ErrorType will be set. |
| HasError bool `json:"has_error"` |
| // If flexible or error syntax are used, this is the success variant of the |
| // result union. |
| ValueType *Type `json:"maybe_response_success_type,omitempty"` |
| // If error syntax is used, this is error variant of the result union. |
| ErrorType *Type `json:"maybe_response_err_type,omitempty"` |
| } |
| |
| // GetRequestPayloadIdentifier retrieves the identifier that points to the |
| // declaration of the request payload. |
| func (m *Method) GetRequestPayloadIdentifier() (EncodedCompoundIdentifier, bool) { |
| if m.RequestPayload == nil { |
| return "", false |
| } |
| return m.RequestPayload.Identifier, true |
| } |
| |
| // GetResponsePayloadIdentifier retrieves the identifier that points to the |
| // declaration of the response payload. |
| func (m *Method) GetResponsePayloadIdentifier() (EncodedCompoundIdentifier, bool) { |
| if m.ResponsePayload == nil { |
| return "", false |
| } |
| return m.ResponsePayload.Identifier, true |
| } |
| |
| // Helper for getting whether the method is strict. Strict is optional while |
| // unknown interactions are experimental, and if strict is missing it should be |
| // treated as true. |
| // TODO(https://fxbug.dev/42169590): replace this method with direct access to the Strict |
| // field, once it is required. |
| func (m *Method) IsStrict() bool { |
| return m.MaybeStrict == nil || *m.MaybeStrict |
| } |
| |
| // IsFlexible is the inverse of IsStrict. Convenience for templates so they |
| // don't have to use (not .IsStrict) |
| func (m *Method) IsFlexible() bool { |
| return !m.IsStrict() |
| } |
| |
| func (m *Method) HasRequestPayload() bool { |
| return m.RequestPayload != nil |
| } |
| |
| func (m *Method) HasResponsePayload() bool { |
| return m.ResponsePayload != nil |
| } |
| |
| // HasResultUnion returns true if the method has a result union response, i.e. |
| // the method is two-way and it is flexible or uses error syntax. |
| func (m *Method) HasResultUnion() bool { |
| return m.ValueType != nil |
| } |
| |
| // HasFrameworkError returns true if the method uses a result union with |
| // framework_err variant. This is true if it is a flexible two-way method. |
| func (m *Method) HasFrameworkError() bool { |
| return m.HasRequest && m.HasResponse && m.IsFlexible() |
| } |
| |
| // Enum represents a FIDL declaration of an enum. |
| type Enum struct { |
| layoutDecl |
| Type PrimitiveSubtype `json:"type"` |
| Members []EnumMember `json:"members"` |
| Strictness `json:"strict"` |
| RawUnknownValue int64OrUint64 `json:"maybe_unknown_value"` |
| } |
| |
| // UnknownValueAsInt64 retrieves the unknown value. Succeeds only for signed |
| // flexible enums. |
| func (enum *Enum) UnknownValueAsInt64() (int64, error) { |
| if enum.IsStrict() { |
| return 0, fmt.Errorf("cannot retrieve unknown value of strict enum") |
| } |
| if enum.Type.IsUnsigned() { |
| return 0, fmt.Errorf("cannot retrieve signed unknown value of unsigned flexible enum") |
| } |
| return enum.RawUnknownValue.readInt64(), nil |
| } |
| |
| // UnknownValueAsUint64 retrieves the unknown value. Succeeds only for unsigned |
| // flexible enums. |
| func (enum *Enum) UnknownValueAsUint64() (uint64, error) { |
| if enum.IsStrict() { |
| return 0, fmt.Errorf("cannot retrieve unknown value of strict enum") |
| } |
| if enum.Type.IsSigned() { |
| return 0, fmt.Errorf("cannot retrieve unsigned unknown value of signed flexible enum") |
| } |
| return enum.RawUnknownValue.readUint64(), nil |
| } |
| |
| // UnknownValueForTmpl retrieves the signed or unsigned unknown value. Panics |
| // if called on a strict enum. |
| func (enum *Enum) UnknownValueForTmpl() interface{} { |
| if enum.Type.IsSigned() { |
| unknownValue, err := enum.UnknownValueAsInt64() |
| if err != nil { |
| panic(err) |
| } |
| return unknownValue |
| } |
| |
| unknownValue, err := enum.UnknownValueAsUint64() |
| if err != nil { |
| panic(err) |
| } |
| return unknownValue |
| } |
| |
| // EnumMember represents a single variant in a FIDL enum. |
| type EnumMember struct { |
| member |
| Value Constant `json:"value"` |
| } |
| |
| // IsUnknown indicates whether this member represents a custom unknown flexible |
| // enum member. |
| func (member *EnumMember) IsUnknown() bool { |
| return member.HasAttribute("Unknown") |
| } |
| |
| // Bits represents a FIDL declaration of a bits. |
| type Bits struct { |
| layoutDecl |
| Type Type `json:"type"` |
| Mask string `json:"mask"` |
| Members []BitsMember `json:"members"` |
| Strictness `json:"strict"` |
| } |
| |
| // BitsMember represents a single variant in a FIDL bits. |
| type BitsMember struct { |
| member |
| Value Constant `json:"value"` |
| } |
| |
| // Const represents a FIDL declaration of a named constant. |
| type Const struct { |
| decl |
| Type Type `json:"type"` |
| Value Constant `json:"value"` |
| } |
| |
| // Resource gives the declaration of a FIDL resource. |
| type Resource struct { |
| decl |
| Type Type `json:"type"` |
| Properties []ResourceProperty `json:"properties"` |
| } |
| |
| type ResourceProperty struct { |
| decl |
| Type Type `json:"type"` |
| } |
| |
| // Strictness represents whether a FIDL object is strict or flexible. See |
| // <https://fuchsia.dev/fuchsia-src/development/languages/fidl/reference/ftp/ftp-033> for more |
| // information. |
| type Strictness bool |
| |
| const ( |
| IsFlexible Strictness = false |
| IsStrict Strictness = true |
| ) |
| |
| // IsStrict indicates whether this type is strict. |
| func (s Strictness) IsStrict() bool { |
| return s == IsStrict |
| } |
| |
| // IsFlexible indicates whether this type is flexible. |
| func (s Strictness) IsFlexible() bool { |
| return s == IsFlexible |
| } |
| |
| // Resourceness represents whether a FIDL object may contain any resource types, |
| // such as handles. See https://fuchsia.dev/fuchsia-src/contribute/governance/fidl/ftp/ftp-057 |
| // for more information. |
| type Resourceness bool |
| |
| const ( |
| IsResourceType Resourceness = true |
| IsValueType Resourceness = false |
| ) |
| |
| // IsResourceType indicates whether this type is marked as a resource type |
| func (r Resourceness) IsResourceType() bool { |
| return r == IsResourceType |
| } |
| |
| // IsValueType indicates whether this type is not marked as a resource type |
| func (r Resourceness) IsValueType() bool { |
| return r == IsValueType |
| } |
| |
| type DeclType string |
| |
| // Note: Keep `DeclType` values in sync with `GetDeclType` below! |
| const ( |
| LibraryDeclType DeclType = "library" |
| |
| AliasDeclType DeclType = "alias" |
| BitsDeclType DeclType = "bits" |
| ConstDeclType DeclType = "const" |
| EnumDeclType DeclType = "enum" |
| NewTypeDeclType DeclType = "new_type" |
| ProtocolDeclType DeclType = "protocol" |
| ResourceDeclType DeclType = "resource" |
| ServiceDeclType DeclType = "service" |
| StructDeclType DeclType = "struct" |
| TableDeclType DeclType = "table" |
| UnionDeclType DeclType = "union" |
| OverlayDeclType DeclType = "overlay" |
| ) |
| |
| func GetDeclType(decl Decl) DeclType { |
| switch decl.(type) { |
| case *Const: |
| return ConstDeclType |
| case *Bits: |
| return BitsDeclType |
| case *Enum: |
| return EnumDeclType |
| case *Resource: |
| return ResourceDeclType |
| case *Protocol: |
| return ProtocolDeclType |
| case *Service: |
| return ServiceDeclType |
| case *Struct: |
| return StructDeclType |
| case *Table: |
| return TableDeclType |
| case *Union: |
| return UnionDeclType |
| case *Alias: |
| return AliasDeclType |
| case *NewType: |
| return NewTypeDeclType |
| case *Overlay: |
| return OverlayDeclType |
| } |
| panic(fmt.Sprintf("unhandled declaration type: %s", reflect.TypeOf(decl).Name())) |
| } |
| |
| type DeclInfo struct { |
| Type DeclType `json:"kind"` |
| // Present for structs, tables, and unions. |
| *Resourceness `json:"resource,omitempty"` |
| } |
| |
| type DeclMap map[EncodedCompoundIdentifier]DeclType |
| type DeclInfoMap map[EncodedCompoundIdentifier]DeclInfo |
| |
| func (dt DeclType) IsPrimitive() bool { |
| switch dt { |
| case BitsDeclType, EnumDeclType: |
| return true |
| } |
| |
| return false |
| } |
| |
| // LookupResourceness looks up whether a type is a value or resource. |
| // TODO(https://fxbug.dev/42156522): Store this on types directly. |
| func (m DeclInfoMap) LookupResourceness(t Type) Resourceness { |
| switch t.Kind { |
| case PrimitiveType, StringType, InternalType: |
| return IsValueType |
| case HandleType, RequestType: |
| return IsResourceType |
| case ArrayType, VectorType: |
| return m.LookupResourceness(*t.ElementType) |
| case ZxExperimentalPointerType: |
| return m.LookupResourceness(*t.PointeeType) |
| case IdentifierType: |
| info, ok := m[t.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("identifier not found: %s", t.Identifier)) |
| } |
| switch info.Type { |
| case BitsDeclType, EnumDeclType: |
| return IsValueType |
| case ProtocolDeclType: |
| return IsResourceType |
| case StructDeclType, TableDeclType, UnionDeclType, OverlayDeclType: |
| return *info.Resourceness |
| default: |
| panic(fmt.Sprintf("unexpected decl type: %s", info.Type)) |
| } |
| default: |
| panic(fmt.Sprintf("unknown type kind: %s", t.Kind)) |
| } |
| } |
| |
| // Library represents a FIDL dependency on a separate library. |
| type Library struct { |
| Name EncodedLibraryIdentifier `json:"name"` |
| Decls DeclInfoMap `json:"declarations"` |
| } |
| |
| // Platform identifies a group of FIDL libraries that are versioned together. |
| type Platform string |
| |
| // Version represents a version of a platform. |
| // It is either a decimal number, "HEAD", or "LEGACY". |
| type Version string |
| |
| // Root is the top-level object for a FIDL library. |
| // It contains lists of all declarations and dependencies within the library. |
| type Root struct { |
| Attributes |
| Name EncodedLibraryIdentifier `json:"name"` |
| Platform Platform `json:"platform"` |
| Available map[Platform]Version `json:"available"` |
| Experiments Experiments `json:"experiments,omitempty"` |
| Consts []Const `json:"const_declarations"` |
| Bits []Bits `json:"bits_declarations"` |
| Enums []Enum `json:"enum_declarations"` |
| Resources []Resource `json:"experimental_resource_declarations"` |
| Protocols []Protocol `json:"protocol_declarations"` |
| Services []Service `json:"service_declarations"` |
| Structs []Struct `json:"struct_declarations"` |
| ExternalStructs []Struct `json:"external_struct_declarations"` |
| Tables []Table `json:"table_declarations"` |
| Unions []Union `json:"union_declarations"` |
| Overlays []Overlay `json:"overlay_declarations"` |
| Aliases []Alias `json:"alias_declarations"` |
| NewTypes []NewType `json:"new_type_declarations"` |
| DeclOrder []EncodedCompoundIdentifier `json:"declaration_order"` |
| Decls DeclMap `json:"declarations"` |
| Libraries []Library `json:"library_dependencies"` |
| } |
| |
| // ForEachDecl calls a provided callback on each associated declaration. Logic |
| // that needs to iterate over all declarations should rely on this method as |
| // opposed to hardcoding the known (at the time) set of declaration types. |
| func (r *Root) ForEachDecl(cb func(Decl)) { |
| for i := range r.Consts { |
| cb(&r.Consts[i]) |
| } |
| for i := range r.Bits { |
| cb(&r.Bits[i]) |
| } |
| for i := range r.Enums { |
| cb(&r.Enums[i]) |
| } |
| for i := range r.Resources { |
| cb(&r.Resources[i]) |
| } |
| for i := range r.Protocols { |
| cb(&r.Protocols[i]) |
| } |
| for i := range r.Services { |
| cb(&r.Services[i]) |
| } |
| for i := range r.Structs { |
| cb(&r.Structs[i]) |
| } |
| for i := range r.ExternalStructs { |
| cb(&r.ExternalStructs[i]) |
| } |
| for i := range r.Tables { |
| cb(&r.Tables[i]) |
| } |
| for i := range r.Unions { |
| cb(&r.Unions[i]) |
| } |
| for i := range r.Aliases { |
| cb(&r.Aliases[i]) |
| } |
| for i := range r.NewTypes { |
| cb(&r.NewTypes[i]) |
| } |
| for i := range r.Overlays { |
| cb(&r.Overlays[i]) |
| } |
| } |
| |
| // DeclInfo returns information on the FIDL library's local and imported |
| // declarations. |
| func (r *Root) DeclInfo() DeclInfoMap { |
| m := DeclInfoMap{} |
| r.ForEachDecl(func(decl Decl) { |
| info := DeclInfo{ |
| Type: GetDeclType(decl), |
| } |
| if resDecl, ok := decl.(ResourceableLayoutDecl); ok { |
| info.Resourceness = new(Resourceness) |
| *info.Resourceness = resDecl.GetResourceness() |
| } |
| m[decl.GetName()] = info |
| }) |
| for _, l := range r.Libraries { |
| for k, v := range l.Decls { |
| m[k] = v |
| } |
| } |
| return m |
| } |
| |
| type EncodedCompoundIdentifierSet map[EncodedCompoundIdentifier]struct{} |
| |
| // filter returns a new Root that excludes elements e where either keep(e) is |
| // false or e is an anonymous layout nested inside an excluded declaration. |
| func (r *Root) filter(keep func(Element) bool) Root { |
| var excluded []scopedNamingContext |
| excludedIfAnonymous := make(map[EncodedCompoundIdentifier]struct{}) |
| r.ForEachDecl(func(decl Decl) { |
| switch decl := decl.(type) { |
| case LayoutDecl: |
| if !keep(decl) { |
| excluded = append(excluded, scopedNamingContext{decl.GetName().LibraryName(), decl.GetNamingContext()}) |
| } |
| case *Protocol: |
| protocolName := string(decl.Name.Parse().Name) |
| if !keep(decl) { |
| excluded = append(excluded, scopedNamingContext{decl.Name.LibraryName(), []string{protocolName}}) |
| break |
| } |
| for _, m := range decl.Methods { |
| if !keep(m) { |
| excluded = append(excluded, scopedNamingContext{ |
| decl.Name.LibraryName(), |
| []string{protocolName, string(m.Name)}, |
| }) |
| if id, ok := m.GetRequestPayloadIdentifier(); ok { |
| excludedIfAnonymous[id] = struct{}{} |
| } |
| if id, ok := m.GetResponsePayloadIdentifier(); ok { |
| excludedIfAnonymous[id] = struct{}{} |
| } |
| } |
| } |
| } |
| }) |
| |
| res := Root{ |
| Name: r.Name, |
| Experiments: r.Experiments, |
| Libraries: r.Libraries, |
| Decls: make(DeclMap, len(r.Decls)), |
| } |
| |
| r.ForEachDecl(func(decl Decl) { |
| if !keep(decl) { |
| return |
| } |
| if layout, ok := decl.(LayoutDecl); ok { |
| context := layout.GetNamingContext() |
| if context.IsAnonymous() { |
| if _, ok := excludedIfAnonymous[layout.GetName()]; ok { |
| return |
| } |
| } |
| scoped := scopedNamingContext{r.Name, context} |
| if scoped.nestedInAny(excluded) { |
| return |
| } |
| } |
| |
| switch v := decl.(type) { |
| case *Const: |
| res.Consts = append(res.Consts, *v) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Bits: |
| newV := *v |
| newV.Members = nil |
| for _, m := range v.Members { |
| if keep(m) { |
| newV.Members = append(newV.Members, m) |
| } |
| } |
| res.Bits = append(res.Bits, newV) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Enum: |
| newV := *v |
| newV.Members = nil |
| for _, m := range v.Members { |
| if keep(m) { |
| newV.Members = append(newV.Members, m) |
| } |
| } |
| res.Enums = append(res.Enums, newV) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Protocol: |
| newV := *v |
| newV.Methods = nil |
| for _, m := range v.Methods { |
| nc := NamingContext{string(v.Name), string(m.Name)} |
| if keep(m) && !(scopedNamingContext{r.Name, nc}.nestedInAny(excluded)) { |
| newV.Methods = append(newV.Methods, m) |
| } |
| } |
| res.Protocols = append(res.Protocols, newV) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Service: |
| newV := *v |
| newV.Members = nil |
| for _, m := range v.Members { |
| if keep(m) { |
| newV.Members = append(newV.Members, m) |
| } |
| } |
| res.Services = append(res.Services, newV) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Struct: |
| newV := *v |
| newV.Members = nil |
| for _, m := range v.Members { |
| if keep(m) { |
| newV.Members = append(newV.Members, m) |
| } |
| } |
| if v.Name.LibraryName() == r.Name { |
| res.Structs = append(res.Structs, newV) |
| } else { |
| res.ExternalStructs = append(res.ExternalStructs, newV) |
| } |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Table: |
| newV := *v |
| newV.Members = nil |
| for _, m := range v.Members { |
| if keep(m) { |
| newV.Members = append(newV.Members, m) |
| } |
| } |
| res.Tables = append(res.Tables, newV) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Union: |
| newV := *v |
| newV.Members = nil |
| for _, m := range v.Members { |
| if keep(m) { |
| newV.Members = append(newV.Members, m) |
| } |
| } |
| res.Unions = append(res.Unions, newV) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| case *Alias: |
| res.Aliases = append(res.Aliases, *v) |
| res.Decls[v.Name] = r.Decls[v.Name] |
| } |
| }) |
| |
| for _, d := range r.DeclOrder { |
| if _, ok := res.Decls[d]; ok { |
| res.DeclOrder = append(res.DeclOrder, d) |
| } |
| } |
| |
| return res |
| } |
| |
| // ForBindings filters out declarations (and nested anonymous layouts) that have |
| // a @bindings_denylist attribute that specifies the given language. It returns |
| // a new Root and does not modify r. |
| func (r *Root) ForBindings(language string) Root { |
| return r.filter(func(e Element) bool { |
| return !e.GetAttributes().BindingsDenylistIncludes(language) |
| }) |
| } |
| |
| // ForTransport filters out protocols and services (and any nested anonymous |
| // layouts) that do not support the given transport. It returns a new Root and |
| // does not modify r. |
| func (r *Root) ForTransport(transport string) Root { |
| return r.filter(func(e Element) bool { |
| switch e := e.(type) { |
| case *Protocol: |
| if _, ok := e.Transports()[transport]; !ok { |
| return false |
| } |
| case *Service: |
| for _, member := range e.Members { |
| if member.Type.ProtocolTransport != transport { |
| return false |
| } |
| } |
| } |
| return true |
| }) |
| } |
| |
| type int64OrUint64 struct { |
| i int64 |
| u uint64 |
| } |
| |
| func (n *int64OrUint64) readInt64() int64 { |
| if n.i != 0 { |
| return n.i |
| } |
| return int64(n.u) |
| } |
| |
| func (n *int64OrUint64) readUint64() uint64 { |
| if n.i != 0 { |
| return uint64(n.i) |
| } |
| return n.u |
| } |
| |
| func Int64OrUint64FromInt64ForTesting(val int64) int64OrUint64 { |
| if val >= 0 { |
| return int64OrUint64{0, uint64(val)} |
| } |
| return int64OrUint64{val, 0} |
| } |
| |
| func Int64OrUint64FromUint64ForTesting(val uint64) int64OrUint64 { |
| return int64OrUint64{0, val} |
| } |
| |
| var _ json.Unmarshaler = (*int64OrUint64)(nil) |
| |
| func (n *int64OrUint64) UnmarshalJSON(data []byte) error { |
| if u, err := strconv.ParseUint(string(data), 10, 64); err == nil { |
| n.u = u |
| return nil |
| } |
| if i, err := strconv.ParseInt(string(data), 10, 64); err == nil { |
| n.i = i |
| return nil |
| } |
| return fmt.Errorf("%s not representable as int64 or uint64", string(data)) |
| } |
| |
| func (n *int64OrUint64) MarshalJSON() ([]byte, error) { |
| if n.i != 0 { |
| return json.Marshal(n.i) |
| } |
| return json.Marshal(n.u) |
| } |