blob: 0d47762a2e1dda5ecf64d4004ed907a6e2aca674 [file] [log] [blame]
// Copyright 2021 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.
// summarize is a library used to produce a FIDL API summary from the FIDL
// intermediate representation (IR) abstract syntax tree. Please refer to the
// README.md file in this repository for usage hints.
package summarize
import (
"bytes"
"encoding/json"
"fmt"
"io"
"sort"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
type summary []Element
// SummaryFormat is a representation of a FIDL API summary.
//
// SummaryFormat implements flag.Setter for convenient use in command-line tools.
type SummaryFormat string
// SummaryFormat constants.
const (
TextSummaryFormat SummaryFormat = "text"
JSONSummaryFormat SummaryFormat = "json"
)
// String implements flag.Setter.
func (f *SummaryFormat) String() string {
return string(*f)
}
// Set implements flag.Setter.
func (f *SummaryFormat) Set(value string) error {
format := SummaryFormat(value)
if _, err := format.formatter(); err != nil {
return err
}
*f = format
return nil
}
func (f SummaryFormat) formatter() (func(io.Writer, summary) error, error) {
switch f {
case TextSummaryFormat:
return formatTextSummary, nil
case JSONSummaryFormat:
return formatJSONSummary, nil
}
return nil, fmt.Errorf("unimplemented summary format %q", string(f))
}
// GenerateSummary summarizes root and returns the serialized result in the given format.
func GenerateSummary(root fidlgen.Root, format SummaryFormat) ([]byte, error) {
var b bytes.Buffer
if err := WriteSummary(&b, root, format); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// WriteSummary summarizes root and writes the serialized result to the given Writer.
func WriteSummary(w io.Writer, root fidlgen.Root, format SummaryFormat) error {
s := summarize(root)
f, err := format.formatter()
if err != nil {
return err
}
return f(w, s)
}
func formatTextSummary(w io.Writer, s summary) error {
for _, e := range s {
fmt.Fprintf(w, "%v\n", e)
}
return nil
}
func formatJSONSummary(w io.Writer, s summary) error {
e := json.NewEncoder(w)
// 4-level indent is chosen to match `fx format-code`.
e.SetIndent("", " ")
e.SetEscapeHTML(false)
return e.Encode(serialize([]Element(s)))
}
// Element describes a single platform surface element. Use Summarize to
// convert a FIDL AST into Elements.
type Element interface {
// Stringer produces a string representation of this Element.
fmt.Stringer
// Member returns true if the Element is a member of something.
Member() bool
// Name returns the fully-qualified name of this Element. For example,
// "library/protocol.Method".
Name() Name
// Serialize converts an Element into a serializable representation.
Serialize() ElementStr
}
// payloadDict contains a mapping of names to their underlying payload layouts,
// represented as parameterizer interfaces.
type payloadDict = map[fidlgen.EncodedCompoundIdentifier]parameterizer
// All implementers of Element.
var _ = []Element{
(*bits)(nil),
(*aConst)(nil),
(*enum)(nil),
(*method)(nil),
(*protocol)(nil),
(*library)(nil),
}
type summarizer struct {
elements elementSlice
symbols symbolTable
}
// addElement adds an element for summarization.
func (s *summarizer) addElement(e Element) {
s.elements = append(s.elements, e)
}
// Elements obtains the API elements in this summarizer.
func (s *summarizer) Elements() []Element {
// Ensure predictable ordering of the reported Elements.
sort.Sort(s.elements)
return s.elements
}
// addUnions adds the elements corresponding to the FIDL unions.
func (s *summarizer) addUnions(unions []fidlgen.Union) {
for _, st := range unions {
for _, m := range st.Members {
if m.Reserved {
// Disregard reserved members.
continue
}
s.addElement(newMember(
&s.symbols, st.Name, m.Name, m.Type, fidlgen.UnionDeclType, nil))
}
s.addElement(
newAggregateWithStrictness(
st.Name, st.Resourceness, fidlgen.UnionDeclType, st.Strictness))
}
}
// addTables adds the elements corresponding to the FIDL tables.
func (s *summarizer) addTables(tables []fidlgen.Table) {
for _, st := range tables {
for _, m := range st.Members {
if m.Reserved {
// Disregard reserved members
continue
}
s.addElement(newMember(&s.symbols, st.Name, m.Name, m.Type, fidlgen.TableDeclType, nil))
}
s.addElement(newAggregate(st.Name, st.Resourceness, fidlgen.TableDeclType))
}
}
// addStructs adds the elements corresponding to the FIDL structs.
func (s *summarizer) addStructs(structs []fidlgen.Struct) {
for _, st := range structs {
for _, m := range st.Members {
s.addElement(newMember(
&s.symbols, st.Name, m.Name, m.Type, fidlgen.StructDeclType, m.MaybeDefaultValue))
}
s.addElement(newAggregate(st.Name, st.Resourceness, fidlgen.StructDeclType))
}
}
// registerStructs registers names of all the structs in the FIDL IR.
func (s *summarizer) registerStructs(structs []fidlgen.Struct) {
for _, st := range structs {
st := st
s.symbols.addStruct(st.Name, &st)
}
}
// registerPayloads registers names of all the payload layouts in the FIDL IR.
func (s *summarizer) registerPayloads(payloads payloadDict) {
s.symbols.addPayloads(payloads)
}
func serialize(e []Element) []ElementStr {
var ret []ElementStr
for _, l := range e {
ret = append(ret, l.Serialize())
}
return ret
}
// processStructs takes the list of structs, and excludes all structs that are
// used as anonymous transactional message bodies, as those are explicitly
// disregarded by the summarizer. All structs used as payloads are added to the
// payload map.
func processStructs(structs []fidlgen.Struct, mtum fidlgen.MethodTypeUsageMap, payloads payloadDict) ([]fidlgen.Struct, payloadDict) {
out := make([]fidlgen.Struct, 0)
for _, s := range structs {
if k, ok := mtum[s.Name]; ok {
payloads[s.Name] = &structPayload{Struct: s}
if s.IsAnonymous() && k != fidlgen.UsedOnlyAsPayload {
// Structs that are only used as anonymous message bodies are not
// included in the summary output because their arguments are flattened
// into the method signature instead.
continue
}
}
out = append(out, s)
}
return out, payloads
}
// processTables takes the list of tables and adds all tables used as payloads
// to the payload map.
func processTables(tables []fidlgen.Table, mtum fidlgen.MethodTypeUsageMap, payloads payloadDict) ([]fidlgen.Table, payloadDict) {
out := make([]fidlgen.Table, 0)
for _, t := range tables {
if _, ok := mtum[t.Name]; ok {
payloads[t.Name] = &tablePayload{Table: t}
}
out = append(out, t)
}
return out, payloads
}
// processUnions takes the list of unions and adds all unions used as payloads
// to the payload map.
func processUnions(unions []fidlgen.Union, mtum fidlgen.MethodTypeUsageMap, payloads payloadDict) ([]fidlgen.Union, payloadDict) {
out := make([]fidlgen.Union, 0)
for _, u := range unions {
if _, ok := mtum[u.Name]; ok {
payloads[u.Name] = &unionPayload{Union: u}
}
out = append(out, u)
}
return out, payloads
}
// Elements returns the API elements found in the supplied AST root in a
// canonical ordering.
func summarize(root fidlgen.Root) summary {
var s summarizer
// Do a first pass of the protocols, creating a map of all names of types that
// are used as a transactional message bodies or payloads to the kind of
// usage (message body, payload, or both).
mtum := root.MethodTypeUsageMap()
s.registerStructs(root.Structs)
s.registerStructs(root.ExternalStructs)
s.registerProtocolNames(root.Protocols)
payloads := payloadDict{}
structs, payloads := processStructs(root.Structs, mtum, payloads)
tables, payloads := processTables(root.Tables, mtum, payloads)
unions, payloads := processUnions(root.Unions, mtum, payloads)
_, payloads = processStructs(root.ExternalStructs, mtum, payloads)
s.registerPayloads(payloads)
s.addConsts(root.Consts)
s.addBits(root.Bits)
s.addEnums(root.Enums)
s.addStructs(structs)
s.addTables(tables)
s.addUnions(unions)
s.addProtocols(root.Protocols)
s.addElement(library{r: root})
return summary(s.Elements())
}