blob: 2c3c7eb86faab17f5a2187a7ee01c4e47b76486c [file] [log] [blame]
// 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 ir
import (
"fidl/compiler/backend/common"
"fidl/compiler/backend/types"
"fmt"
"log"
"strings"
)
const (
OutOfLineSuffix = "OutOfLine"
InLineSuffix = "InLine"
RequestSuffix = "Request"
ResponseSuffix = "Response"
EventSuffix = "Event"
HandlesSuffix = "Handles"
)
// Type represents a syzkaller type including type-options.
type Type string
// Enum represents a set of syzkaller flags
type Enum struct {
Name string
Type string
Members []string
}
// Struct represents a syzkaller struct.
type Struct struct {
Name string
Members []StructMember
}
// StructMember represents a member of a syzkaller struct.
type StructMember struct {
Name string
Type Type
}
// Union represents a syzkaller union.
type Union struct {
Name string
Members []StructMember
VarLen bool
}
// Interface represents a FIDL interface in terms of syzkaller structures.
type Interface struct {
Name string
// ServiceNameString is the string service name for this FIDL interface.
ServiceNameString string
// Methods is a list of methods for this FIDL interface.
Methods []Method
}
// Method represents a method of a FIDL interface in terms of syzkaller syscalls.
type Method struct {
// Ordinal is the ordinal for this method.
Ordinal types.Ordinal
// Name is the name of the Method, including the interface name as a prefix.
Name string
// Request represents a struct containing the request parameters.
Request *Struct
// RequestHandles represents a struct containing the handles in the request parameters.
RequestHandles *Struct
// Response represents an optional struct containing the response parameters.
Response *Struct
// ResponseHandles represents a struct containing the handles in the response parameters.
ResponseHandles *Struct
// Structs contain all the structs generated during depth-first traversal of Request/Response.
Structs []Struct
// Unions contain all the unions generated during depth-first traversal of Request/Response.
Unions []Union
}
// Root is the root of the syzkaller backend IR structure.
type Root struct {
// Name is the name of the library.
Name string
// C header file path to be included in syscall description.
HeaderPath string
// Interfaces represent the list of FIDL interfaces represented as a collection of syskaller syscall descriptions.
Interfaces []Interface
// Structs correspond to syzkaller structs.
Structs []Struct
// Unions correspond to syzkaller unions.
Unions []Union
// Enums correspond to syzkaller flags.
Enums []Enum
}
type StructMap map[types.EncodedCompoundIdentifier]types.Struct
type UnionMap map[types.EncodedCompoundIdentifier]types.Union
type EnumMap map[types.EncodedCompoundIdentifier]types.Enum
type compiler struct {
// decls contains all top-level declarations for the FIDL source.
decls types.DeclMap
// structs contain all top-level struct definitions for the FIDL source.
structs StructMap
// unions contain all top-level union definitions for the FIDL source.
unions UnionMap
// enums contain all top-level enum definitions for the FIDL source.
enums EnumMap
// library is the identifier for the current library.
library types.LibraryIdentifier
}
var reservedWords = map[string]bool{
"array": true,
"buffer": true,
"int8": true,
"int16": true,
"int32": true,
"int64": true,
"intptr": true,
"ptr": true,
"type": true,
"len": true,
"string": true,
"stringnoz": true,
"const": true,
"in": true,
"out": true,
"flags": true,
"bytesize": true,
"bitsize": true,
"text": true,
"void": true,
}
var primitiveTypes = map[types.PrimitiveSubtype]string{
types.Bool: "int8",
types.Int8: "int8",
types.Int16: "int16",
types.Int32: "int32",
types.Int64: "int64",
types.Uint8: "int8",
types.Uint16: "int16",
types.Uint32: "int32",
types.Uint64: "int64",
types.Float32: "int32",
types.Float64: "int64",
}
var handleSubtypes = map[types.HandleSubtype]string{
types.Handle: "zx_handle",
types.Process: "zx_process",
types.Thread: "zx_thread",
types.Vmo: "zx_vmo",
types.Channel: "zx_chan",
types.Event: "zx_event",
types.Port: "zx_port",
types.Interrupt: "zx_interrupt",
types.Log: "zx_log",
types.Socket: "zx_socket",
types.Resource: "zx_resource",
types.Eventpair: "zx_eventpair",
types.Job: "zx_job",
types.Vmar: "zx_vmar",
types.Fifo: "zx_fifo",
types.Guest: "zx_guest",
types.Time: "zx_timer",
}
func isReservedWord(str string) bool {
_, ok := reservedWords[str]
return ok
}
func changeIfReserved(val types.Identifier, ext string) string {
str := string(val) + ext
if isReservedWord(str) {
return str + "_"
}
return str
}
func formatLibrary(library types.LibraryIdentifier, sep string) string {
parts := []string{}
for _, part := range library {
parts = append(parts, string(part))
}
return changeIfReserved(types.Identifier(strings.Join(parts, sep)), "")
}
func formatLibraryPath(library types.LibraryIdentifier) string {
return formatLibrary(library, "/")
}
func (c *compiler) compileIdentifier(id types.Identifier, ext string) string {
str := string(id)
str = common.ToSnakeCase(str)
return changeIfReserved(types.Identifier(str), ext)
}
func (c *compiler) compileCompoundIdentifier(eci types.EncodedCompoundIdentifier, ext string) string {
val := types.ParseCompoundIdentifier(eci)
strs := []string{}
strs = append(strs, formatLibrary(val.Library, "_"))
strs = append(strs, changeIfReserved(val.Name, ext))
return strings.Join(strs, "_")
}
func (c *compiler) compilePrimitiveSubtype(val types.PrimitiveSubtype) Type {
t, ok := primitiveTypes[val]
if !ok {
log.Fatal("Unknown primitive type: ", val)
}
return Type(t)
}
func (c *compiler) compilePrimitiveSubtypeRange(val types.PrimitiveSubtype, valRange string) Type {
return Type(fmt.Sprintf("%s[%s]", c.compilePrimitiveSubtype(val), valRange))
}
func (c *compiler) compileHandleSubtype(val types.HandleSubtype) Type {
if t, ok := handleSubtypes[val]; ok {
return Type(t)
}
log.Fatal("Unknown handle type: ", val)
return Type("")
}
func (c *compiler) compileEnum(val types.Enum) Enum {
e := Enum{
c.compileCompoundIdentifier(val.Name, ""),
string(c.compilePrimitiveSubtype(val.Type)),
[]string{},
}
for _, v := range val.Members {
e.Members = append(e.Members, fmt.Sprintf("%s_%s", e.Name, v.Name))
}
return e
}
func (c *compiler) compileStructMember(p types.StructMember) (StructMember, *StructMember, *StructMember) {
var i StructMember
var o *StructMember
var h *StructMember
switch p.Type.Kind {
case types.PrimitiveType:
i = StructMember{
Type: c.compilePrimitiveSubtype(p.Type.PrimitiveSubtype),
Name: c.compileIdentifier(p.Name, ""),
}
case types.HandleType:
i = StructMember{
Type: Type("flags[fidl_handle_presence, int32]"),
Name: c.compileIdentifier(p.Name, ""),
}
// Out-of-line handles
h = &StructMember{
Type: c.compileHandleSubtype(p.Type.HandleSubtype),
Name: c.compileIdentifier(p.Name, ""),
}
case types.RequestType:
i = StructMember{
Type: Type("flags[fidl_handle_presence, int32]"),
Name: c.compileIdentifier(p.Name, ""),
}
// Out-of-line handles
h = &StructMember{
Type: Type(fmt.Sprintf("zx_chan_%s_server", c.compileCompoundIdentifier(p.Type.RequestSubtype, ""))),
Name: c.compileIdentifier(p.Name, ""),
}
case types.ArrayType:
inLine, outOfLine, handle := c.compileStructMember(types.StructMember{
Name: types.Identifier(c.compileIdentifier(p.Name, OutOfLineSuffix)),
Type: (*p.Type.ElementType),
})
i = StructMember{
Type: Type(fmt.Sprintf("array[%s, %v]", inLine.Type, *p.Type.ElementCount)),
Name: c.compileIdentifier(p.Name, InLineSuffix),
}
// Variable-size, out-of-line data
if outOfLine != nil {
o = &StructMember{
Type: Type(fmt.Sprintf("array[%s, %v]", outOfLine.Type, *p.Type.ElementCount)),
Name: c.compileIdentifier(p.Name, OutOfLineSuffix),
}
}
// Out-of-line handles
if handle != nil {
h = &StructMember{
Type: Type(fmt.Sprintf("array[%s, %v]", handle.Type, *p.Type.ElementCount)),
Name: c.compileIdentifier(p.Name, HandlesSuffix),
}
}
case types.StringType:
// Constant-size, in-line data
i = StructMember{
Type: Type("fidl_string"),
Name: c.compileIdentifier(p.Name, InLineSuffix),
}
// Variable-size, out-of-line data
o = &StructMember{
Type: Type("fidl_aligned[stringnoz]"),
Name: c.compileIdentifier(p.Name, OutOfLineSuffix),
}
case types.VectorType:
// Constant-size, in-line data
i = StructMember{
Type: Type("fidl_vector"),
Name: c.compileIdentifier(p.Name, InLineSuffix),
}
// Variable-size, out-of-line data
inLine, outOfLine, handle := c.compileStructMember(types.StructMember{
Name: types.Identifier(c.compileIdentifier(p.Name, OutOfLineSuffix)),
Type: (*p.Type.ElementType),
})
o = &StructMember{
Type: Type(fmt.Sprintf("array[%s]", inLine.Type)),
Name: c.compileIdentifier(p.Name, OutOfLineSuffix),
}
if outOfLine != nil {
o = &StructMember{
Type: Type(fmt.Sprintf("parallel_array[%s, %s]", inLine.Type, outOfLine.Type)),
Name: c.compileIdentifier(p.Name, OutOfLineSuffix),
}
}
// Out-of-line handles
if handle != nil {
h = &StructMember{
Type: Type(fmt.Sprintf("array[%s]", handle.Type)),
Name: c.compileIdentifier(p.Name, ""),
}
}
case types.IdentifierType:
declType, ok := c.decls[p.Type.Identifier]
if !ok {
log.Fatal("Unknown identifier: ", p.Type.Identifier)
}
switch declType {
case types.EnumDeclType:
i = StructMember{
Type: Type(fmt.Sprintf("flags[%s, %s]", c.compileCompoundIdentifier(p.Type.Identifier, ""), c.compilePrimitiveSubtype(c.enums[p.Type.Identifier].Type))),
Name: c.compileIdentifier(p.Name, ""),
}
case types.InterfaceDeclType:
i = StructMember{
Type: Type("flags[fidl_handle_presence, int32]"),
Name: c.compileIdentifier(p.Name, ""),
}
// Out-of-line handles
h = &StructMember{
Type: Type(fmt.Sprintf("zx_chan_%s_client", c.compileCompoundIdentifier(p.Type.Identifier, ""))),
Name: c.compileIdentifier(p.Name, ""),
}
case types.UnionDeclType:
_, outOfLine, handles := c.compileUnion(c.unions[p.Type.Identifier])
// Constant-size, in-line data
t := c.compileCompoundIdentifier(p.Type.Identifier, InLineSuffix)
i = StructMember{
Type: Type(t),
Name: c.compileIdentifier(p.Name, InLineSuffix),
}
// Variable-size, out-of-line data
if outOfLine != nil {
t := c.compileCompoundIdentifier(p.Type.Identifier, OutOfLineSuffix)
o = &StructMember{
Type: Type(t),
Name: c.compileIdentifier(p.Name, OutOfLineSuffix),
}
}
// Out-of-line handles
if handles != nil {
t := c.compileCompoundIdentifier(p.Type.Identifier, HandlesSuffix)
h = &StructMember{
Type: Type(t),
Name: c.compileIdentifier(p.Name, ""),
}
}
case types.StructDeclType:
_, outOfLine, handles := c.compileStruct(c.structs[p.Type.Identifier])
// Constant-size, in-line data
t := c.compileCompoundIdentifier(p.Type.Identifier, InLineSuffix)
i = StructMember{
Type: Type(t),
Name: c.compileIdentifier(p.Name, InLineSuffix),
}
// Variable-size, out-of-line data
if outOfLine != nil {
t := c.compileCompoundIdentifier(p.Type.Identifier, OutOfLineSuffix)
o = &StructMember{
Type: Type(t),
Name: c.compileIdentifier(p.Name, OutOfLineSuffix),
}
}
// Out-of-line handles
if handles != nil {
t := c.compileCompoundIdentifier(p.Type.Identifier, HandlesSuffix)
h = &StructMember{
Type: Type(t),
Name: c.compileIdentifier(p.Name, ""),
}
}
}
}
return i, o, h
}
func (c *compiler) compileMessageHeader(Ordinal types.Ordinal) StructMember {
return StructMember{
Type: Type(fmt.Sprintf("fidl_message_header[%d]", Ordinal)),
Name: "hdr",
}
}
func (c *compiler) compileStruct(p types.Struct) ([]StructMember, []StructMember, []StructMember) {
var i, o, h []StructMember
for _, m := range p.Members {
inLine, outOfLine, handles := c.compileStructMember(m)
i = append(i, inLine)
if outOfLine != nil {
o = append(o, *outOfLine)
}
if handles != nil {
h = append(h, *handles)
}
}
if len(o) == 0 {
o = append(o, StructMember{
Name: "void",
Type: "void",
})
}
if len(h) == 0 {
h = append(h, StructMember{
Name: "void",
Type: "void",
})
}
return i, o, h
}
func (c *compiler) compileUnion(p types.Union) ([]StructMember, []StructMember, []StructMember) {
var i, o, h []StructMember
for _, m := range p.Members {
inLine, outOfLine, handles := c.compileStructMember(types.StructMember{
Type: m.Type,
Name: m.Name,
Offset: m.Offset,
})
i = append(i, StructMember{
Type: Type(fmt.Sprintf("fidl_union_member[%sTag_%s, %s]", c.compileCompoundIdentifier(p.Name, ""), m.Name, inLine.Type)),
Name: inLine.Name,
})
if outOfLine != nil {
o = append(o, *outOfLine)
}
if handles != nil {
h = append(h, *handles)
}
}
return i, o, h
}
func (c *compiler) compileParameters(name string, ordinal types.Ordinal, params []types.Parameter) (Struct, Struct) {
var args types.Struct
for _, p := range params {
args.Members = append(args.Members, types.StructMember{
Type: p.Type,
Name: p.Name,
Offset: p.Offset,
})
}
i, o, h := c.compileStruct(args)
if len(o) == 1 && o[0].Type == "void" {
o = []StructMember{}
}
msg := Struct{
Name: name,
Members: append(append([]StructMember{c.compileMessageHeader(ordinal)}, i...), o...),
}
handles := Struct{
Name: name + HandlesSuffix,
Members: h,
}
return msg, handles
}
func (c *compiler) compileMethod(ifaceName types.EncodedCompoundIdentifier, val types.Method) Method {
methodName := c.compileCompoundIdentifier(ifaceName, string(val.Name))
r := Method{
Name: methodName,
Ordinal: val.Ordinal,
}
if val.HasRequest {
request, requestHandles := c.compileParameters(r.Name+RequestSuffix, r.Ordinal, val.Request)
r.Request = &request
r.RequestHandles = &requestHandles
}
// For response, we only extract handles for now.
if val.HasResponse {
suffix := ResponseSuffix
if !val.HasRequest {
suffix = EventSuffix
}
response, responseHandles := c.compileParameters(r.Name+suffix, r.Ordinal, val.Response)
r.Response = &response
r.ResponseHandles = &responseHandles
}
return r
}
func (c *compiler) compileInterface(val types.Interface) Interface {
r := Interface{
Name: c.compileCompoundIdentifier(val.Name, ""),
ServiceNameString: strings.Trim(val.GetServiceName(), "\""),
}
for _, v := range val.Methods {
r.Methods = append(r.Methods, c.compileMethod(val.Name, v))
}
return r
}
func Compile(fidlData types.Root) Root {
root := Root{}
libraryName := types.ParseLibraryName(fidlData.Name)
c := compiler{
decls: fidlData.Decls,
structs: make(StructMap),
unions: make(UnionMap),
enums: make(EnumMap),
library: libraryName,
}
root.HeaderPath = fmt.Sprintf("%s/c/fidl.h", formatLibraryPath(libraryName))
for _, v := range fidlData.Enums {
c.enums[v.Name] = v
root.Enums = append(root.Enums, c.compileEnum(v))
}
for _, v := range fidlData.Structs {
c.structs[v.Name] = v
i, o, h := c.compileStruct(v)
root.Structs = append(root.Structs, Struct{
Name: c.compileCompoundIdentifier(v.Name, InLineSuffix),
Members: i,
})
root.Structs = append(root.Structs, Struct{
Name: c.compileCompoundIdentifier(v.Name, OutOfLineSuffix),
Members: o,
})
root.Structs = append(root.Structs, Struct{
Name: c.compileCompoundIdentifier(v.Name, HandlesSuffix),
Members: h,
})
}
for _, v := range fidlData.Unions {
c.unions[v.Name] = v
i, o, h := c.compileUnion(v)
root.Unions = append(root.Unions, Union{
Name: c.compileCompoundIdentifier(v.Name, InLineSuffix),
Members: i,
})
if len(o) == 0 {
o = append(o, StructMember{
Name: "void",
Type: "void",
})
}
if len(h) == 0 {
h = append(h, StructMember{
Name: "void",
Type: "void",
})
}
root.Unions = append(root.Unions, Union{
Name: c.compileCompoundIdentifier(v.Name, OutOfLineSuffix),
Members: o,
VarLen: true,
})
root.Unions = append(root.Unions, Union{
Name: c.compileCompoundIdentifier(v.Name, HandlesSuffix),
Members: h,
VarLen: true,
})
}
for _, v := range fidlData.Interfaces {
root.Interfaces = append(root.Interfaces, c.compileInterface(v))
}
exists := make(map[string]bool)
for _, i := range root.Interfaces {
for _, m := range i.Methods {
for _, s := range m.Structs {
if _, ok := exists[s.Name]; !ok {
root.Structs = append(root.Structs, s)
exists[s.Name] = true
}
}
for _, s := range m.Unions {
if _, ok := exists[s.Name]; !ok {
root.Unions = append(root.Unions, s)
exists[s.Name] = true
}
}
}
}
return root
}