| // Copyright 2019 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 llcpp |
| |
| import ( |
| "fmt" |
| "io" |
| "strconv" |
| "strings" |
| "text/template" |
| |
| fidlcommon "fidl/compiler/backend/common" |
| fidlir "fidl/compiler/backend/types" |
| gidlir "gidl/ir" |
| gidlmixer "gidl/mixer" |
| ) |
| |
| var tmpl = template.Must(template.New("tmpl").Parse(` |
| #include <iostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <conformance/llcpp/fidl.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/fidl/llcpp/tests/test_utils.h" |
| |
| {{ range .EncodeSuccessCases }} |
| TEST(Conformance, {{ .Name }}_Encode) { |
| {{ .ValueBuild }} |
| const auto expected = std::vector<uint8_t>{ |
| {{ .Bytes }} |
| }; |
| EXPECT_TRUE(llcpp_conformance_utils::EncodeSuccess(&{{ .ValueVar }}, expected)); |
| } |
| {{ end }} |
| |
| {{ range .DecodeSuccessCases }} |
| TEST(Conformance, {{ .Name }}_Decode) { |
| {{ .ValueBuild }} |
| auto bytes = std::vector<uint8_t>{ |
| {{ .Bytes }} |
| }; |
| EXPECT_TRUE(llcpp_conformance_utils::DecodeSuccess(&{{ .ValueVar }}, std::move(bytes))); |
| } |
| {{ end }} |
| |
| {{ range .EncodeFailureCases }} |
| TEST(Conformance, {{ .Name }}_Encode_Failure) { |
| {{ .ValueBuild }} |
| |
| EXPECT_TRUE(llcpp_conformance_utils::EncodeFailure(&{{ .ValueVar }}, {{ .ErrorCode }})); |
| } |
| {{ end }} |
| |
| {{ range .DecodeFailureCases }} |
| TEST(Conformance, {{ .Name }}_Decode_Failure) { |
| auto bytes = std::vector<uint8_t>{ |
| {{ .Bytes }} |
| }; |
| |
| EXPECT_TRUE(llcpp_conformance_utils::DecodeFailure<{{ .ValueType }}>(std::move(bytes), {{ .ErrorCode }})); |
| } |
| {{ end }} |
| `)) |
| |
| type tmplInput struct { |
| EncodeSuccessCases []encodeSuccessCase |
| DecodeSuccessCases []decodeSuccessCase |
| EncodeFailureCases []encodeFailureCase |
| DecodeFailureCases []decodeFailureCase |
| } |
| |
| type encodeSuccessCase struct { |
| Name, ValueBuild, ValueVar, Bytes string |
| } |
| |
| type decodeSuccessCase struct { |
| Name, ValueBuild, ValueVar, Bytes string |
| } |
| |
| type encodeFailureCase struct { |
| Name, ValueBuild, ValueVar, ErrorCode string |
| } |
| |
| type decodeFailureCase struct { |
| Name, ValueType, Bytes, ErrorCode string |
| } |
| |
| // Generate generates Low-Level C++ tests. |
| func Generate(wr io.Writer, gidl gidlir.All, fidl fidlir.Root) error { |
| encodeSuccessCases, err := encodeSuccessCases(gidl.EncodeSuccess, fidl) |
| if err != nil { |
| return err |
| } |
| decodeSuccessCases, err := decodeSuccessCases(gidl.DecodeSuccess, fidl) |
| if err != nil { |
| return err |
| } |
| encodeFailureCases, err := encodeFailureCases(gidl.EncodeFailure, fidl) |
| if err != nil { |
| return err |
| } |
| decodeFailureCases, err := decodeFailureCases(gidl.DecodeFailure, fidl) |
| if err != nil { |
| return err |
| } |
| return tmpl.Execute(wr, tmplInput{ |
| EncodeSuccessCases: encodeSuccessCases, |
| DecodeSuccessCases: decodeSuccessCases, |
| EncodeFailureCases: encodeFailureCases, |
| DecodeFailureCases: decodeFailureCases, |
| }) |
| } |
| |
| func encodeSuccessCases(gidlEncodeSuccesses []gidlir.EncodeSuccess, fidl fidlir.Root) ([]encodeSuccessCase, error) { |
| var encodeSuccessCases []encodeSuccessCase |
| for _, encodeSuccess := range gidlEncodeSuccesses { |
| decl, err := gidlmixer.ExtractDeclaration(encodeSuccess.Value, fidl) |
| if err != nil { |
| return nil, fmt.Errorf("encode success %s: %s", encodeSuccess.Name, err) |
| } |
| if gidlir.ContainsUnknownField(encodeSuccess.Value) { |
| continue |
| } |
| valueBuild, valueVar := buildValue(encodeSuccess.Value, decl) |
| for _, encoding := range encodeSuccess.Encodings { |
| if !wireFormatSupported(encoding.WireFormat) { |
| continue |
| } |
| encodeSuccessCases = append(encodeSuccessCases, encodeSuccessCase{ |
| Name: testCaseName(encodeSuccess.Name, encoding.WireFormat), |
| ValueBuild: valueBuild, |
| ValueVar: valueVar, |
| Bytes: bytesBuilder(encoding.Bytes), |
| }) |
| } |
| } |
| return encodeSuccessCases, nil |
| } |
| |
| func decodeSuccessCases(gidlDecodeSuccesses []gidlir.DecodeSuccess, fidl fidlir.Root) ([]decodeSuccessCase, error) { |
| var decodeSuccessCases []decodeSuccessCase |
| for _, decodeSuccess := range gidlDecodeSuccesses { |
| decl, err := gidlmixer.ExtractDeclaration(decodeSuccess.Value, fidl) |
| if err != nil { |
| return nil, fmt.Errorf("decode success %s: %s", decodeSuccess.Name, err) |
| } |
| if gidlir.ContainsUnknownField(decodeSuccess.Value) { |
| continue |
| } |
| valueBuild, valueVar := buildValue(decodeSuccess.Value, decl) |
| for _, encoding := range decodeSuccess.Encodings { |
| if !wireFormatSupported(encoding.WireFormat) { |
| continue |
| } |
| decodeSuccessCases = append(decodeSuccessCases, decodeSuccessCase{ |
| Name: testCaseName(decodeSuccess.Name, encoding.WireFormat), |
| ValueBuild: valueBuild, |
| ValueVar: valueVar, |
| Bytes: bytesBuilder(encoding.Bytes), |
| }) |
| } |
| } |
| return decodeSuccessCases, nil |
| } |
| |
| func encodeFailureCases(gidlEncodeFailurees []gidlir.EncodeFailure, fidl fidlir.Root) ([]encodeFailureCase, error) { |
| var encodeFailureCases []encodeFailureCase |
| for _, encodeFailure := range gidlEncodeFailurees { |
| decl, err := gidlmixer.ExtractDeclarationUnsafe(encodeFailure.Value, fidl) |
| if err != nil { |
| return nil, fmt.Errorf("encode failure %s: %s", encodeFailure.Name, err) |
| } |
| valueBuild, valueVar := buildValue(encodeFailure.Value, decl) |
| for _, wireFormat := range encodeFailure.WireFormats { |
| if !wireFormatSupported(wireFormat) { |
| continue |
| } |
| encodeFailureCases = append(encodeFailureCases, encodeFailureCase{ |
| Name: encodeFailure.Name, |
| ValueBuild: valueBuild, |
| ValueVar: valueVar, |
| ErrorCode: llcppErrorCode(encodeFailure.Err), |
| }) |
| } |
| } |
| return encodeFailureCases, nil |
| } |
| |
| func decodeFailureCases(gidlDecodeFailurees []gidlir.DecodeFailure, fidl fidlir.Root) ([]decodeFailureCase, error) { |
| var decodeFailureCases []decodeFailureCase |
| for _, decodeFailure := range gidlDecodeFailurees { |
| for _, encoding := range decodeFailure.Encodings { |
| if !wireFormatSupported(encoding.WireFormat) { |
| continue |
| } |
| decodeFailureCases = append(decodeFailureCases, decodeFailureCase{ |
| Name: decodeFailure.Name, |
| ValueType: llcppType(decodeFailure.Type), |
| Bytes: bytesBuilder(encoding.Bytes), |
| ErrorCode: llcppErrorCode(decodeFailure.Err), |
| }) |
| } |
| } |
| return decodeFailureCases, nil |
| } |
| |
| func wireFormatSupported(wireFormat gidlir.WireFormat) bool { |
| return wireFormat == gidlir.V1WireFormat |
| } |
| |
| func testCaseName(baseName string, wireFormat gidlir.WireFormat) string { |
| return fmt.Sprintf("%s_%s", baseName, fidlcommon.ToUpperCamelCase(wireFormat.String())) |
| } |
| |
| // TODO(fxb/39685) extract out to common library |
| func bytesBuilder(bytes []byte) string { |
| var builder strings.Builder |
| for i, b := range bytes { |
| builder.WriteString(fmt.Sprintf("0x%02x", b)) |
| builder.WriteString(",") |
| if i%8 == 7 { |
| builder.WriteString("\n") |
| } |
| } |
| return builder.String() |
| } |
| |
| func buildValue(value interface{}, decl gidlmixer.Declaration) (string, string) { |
| var builder llcppValueBuilder |
| gidlmixer.Visit(&builder, value, decl) |
| valueBuild := builder.String() |
| valueVar := builder.lastVar |
| return valueBuild, valueVar |
| } |
| |
| type llcppValueBuilder struct { |
| strings.Builder |
| varidx int |
| lastVar string |
| } |
| |
| func (b *llcppValueBuilder) newVar() string { |
| b.varidx++ |
| return fmt.Sprintf("v%d", b.varidx) |
| } |
| |
| func (b *llcppValueBuilder) OnBool(value bool) { |
| newVar := b.newVar() |
| // FIDL_ALIGNDECL is needed to avoid tracking_ptrs that don't have an LSB of 0. |
| b.Builder.WriteString(fmt.Sprintf("FIDL_ALIGNDECL bool %s = %t;\n", newVar, value)) |
| b.lastVar = newVar |
| } |
| |
| func integerTypeName(subtype fidlir.PrimitiveSubtype) string { |
| switch subtype { |
| case "int8": |
| return "int8_t" |
| case "uint8": |
| return "uint8_t" |
| case "int16": |
| return "int16_t" |
| case "uint16": |
| return "uint16_t" |
| case "int32": |
| return "int32_t" |
| case "uint32": |
| return "uint32_t" |
| case "int64": |
| return "int64_t" |
| case "uint64": |
| return "uint64_t" |
| default: |
| panic(fmt.Sprintf("Unexpected subtype %s", string(subtype))) |
| } |
| } |
| |
| func (b *llcppValueBuilder) OnInt64(value int64, typ fidlir.PrimitiveSubtype) { |
| newVar := b.newVar() |
| if value == -9223372036854775808 { |
| // There are no negative integer literals in C++, so need to use arithmetic to create the minimum value. |
| // FIDL_ALIGNDECL is needed to avoid tracking_ptrs that don't have an LSB of 0. |
| b.Builder.WriteString(fmt.Sprintf("FIDL_ALIGNDECL %s %s = -9223372036854775807ll - 1;\n", integerTypeName(typ), newVar)) |
| } else { |
| // FIDL_ALIGNDECL is needed to avoid tracking_ptrs that don't have an LSB of 0. |
| b.Builder.WriteString(fmt.Sprintf("FIDL_ALIGNDECL %s %s = %dll;\n", integerTypeName(typ), newVar, value)) |
| } |
| b.lastVar = newVar |
| } |
| |
| func (b *llcppValueBuilder) OnUint64(value uint64, subtype fidlir.PrimitiveSubtype) { |
| newVar := b.newVar() |
| // FIDL_ALIGNDECL is needed to avoid tracking_ptrs that don't have an LSB of 0. |
| b.Builder.WriteString(fmt.Sprintf("FIDL_ALIGNDECL %s %s = %dull;\n", integerTypeName(subtype), newVar, value)) |
| b.lastVar = newVar |
| } |
| |
| func (b *llcppValueBuilder) OnFloat64(value float64, subtype fidlir.PrimitiveSubtype) { |
| var typename string |
| switch subtype { |
| case fidlir.Float32: |
| typename = "float" |
| case fidlir.Float64: |
| typename = "double" |
| default: |
| panic("unknown floating point type") |
| } |
| newVar := b.newVar() |
| // FIDL_ALIGNDECL is needed to avoid tracking_ptrs that don't have an LSB of 0. |
| b.Builder.WriteString(fmt.Sprintf("FIDL_ALIGNDECL %s %s = %g;\n", typename, newVar, value)) |
| b.lastVar = newVar |
| } |
| |
| func (b *llcppValueBuilder) OnString(value string, decl *gidlmixer.StringDecl) { |
| newVar := b.newVar() |
| // TODO(fxb/39686) Consider Go/C++ escape sequence differences |
| b.Builder.WriteString(fmt.Sprintf( |
| "fidl::StringView %s(%s, %d)\n;", newVar, strconv.Quote(value), len(value))) |
| b.lastVar = newVar |
| } |
| |
| func (b *llcppValueBuilder) OnStruct(value gidlir.Object, decl *gidlmixer.StructDecl) { |
| containerVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf( |
| "llcpp::conformance::%s %s{};\n", value.Name, containerVar)) |
| for _, field := range value.Fields { |
| fieldDecl, _ := decl.ForKey(field.Key) |
| gidlmixer.Visit(b, field.Value, fieldDecl) |
| b.Builder.WriteString(fmt.Sprintf( |
| "%s.%s = std::move(%s);\n", containerVar, field.Key.Name, b.lastVar)) |
| } |
| if decl.IsNullable() { |
| alignedVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("fidl::aligned<%s> %s = std::move(%s);\n", typeNameIgnoreNullable(decl), alignedVar, containerVar)) |
| unownedVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("%s %s = fidl::unowned(&%s);\n", typeName(decl), unownedVar, alignedVar)) |
| b.lastVar = unownedVar |
| } else { |
| b.lastVar = containerVar |
| } |
| } |
| |
| func (b *llcppValueBuilder) OnTable(value gidlir.Object, decl *gidlmixer.TableDecl) { |
| frameVar := b.newVar() |
| |
| b.Builder.WriteString(fmt.Sprintf( |
| "auto %s = llcpp::conformance::%s::Frame();\n", frameVar, value.Name)) |
| |
| builderVar := b.newVar() |
| |
| b.Builder.WriteString(fmt.Sprintf( |
| "auto %s = llcpp::conformance::%s::Builder(fidl::unowned(&%s));\n", builderVar, value.Name, frameVar)) |
| |
| for _, field := range value.Fields { |
| if field.Key.Name == "" { |
| panic("unknown field not supported") |
| } |
| fieldDecl, _ := decl.ForKey(field.Key) |
| gidlmixer.Visit(b, field.Value, fieldDecl) |
| fieldVar := b.lastVar |
| alignedVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("fidl::aligned<%s> %s = std::move(%s);\n", typeName(fieldDecl), alignedVar, fieldVar)) |
| b.Builder.WriteString(fmt.Sprintf( |
| "%s.set_%s(fidl::unowned(&%s));\n", builderVar, field.Key.Name, alignedVar)) |
| |
| } |
| tableVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf( |
| "auto %s = %s.build();\n", tableVar, builderVar)) |
| |
| b.lastVar = tableVar |
| } |
| |
| func (b *llcppValueBuilder) OnXUnion(value gidlir.Object, decl *gidlmixer.XUnionDecl) { |
| containerVar := b.newVar() |
| |
| b.Builder.WriteString(fmt.Sprintf( |
| "llcpp::conformance::%s %s;\n", value.Name, containerVar)) |
| |
| for _, field := range value.Fields { |
| if field.Key.Name == "" { |
| panic("unknown field not supported") |
| } |
| fieldDecl, _ := decl.ForKey(field.Key) |
| gidlmixer.Visit(b, field.Value, fieldDecl) |
| fieldVar := b.lastVar |
| alignedVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("fidl::aligned<%s> %s = std::move(%s);\n", typeName(fieldDecl), alignedVar, fieldVar)) |
| b.Builder.WriteString(fmt.Sprintf( |
| "%s.set_%s(fidl::unowned(&%s));\n", containerVar, field.Key.Name, alignedVar)) |
| } |
| b.lastVar = containerVar |
| } |
| |
| func (b *llcppValueBuilder) OnArray(value []interface{}, decl *gidlmixer.ArrayDecl) { |
| var elements []string |
| elemDecl, _ := decl.Elem() |
| for _, item := range value { |
| gidlmixer.Visit(b, item, elemDecl) |
| elements = append(elements, fmt.Sprintf("std::move(%s)", b.lastVar)) |
| } |
| sliceVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("FIDL_ALIGNDECL auto %s = %s{%s};\n", |
| sliceVar, typeName(decl), strings.Join(elements, ", "))) |
| b.lastVar = sliceVar |
| } |
| |
| func (b *llcppValueBuilder) OnVector(value []interface{}, decl *gidlmixer.VectorDecl) { |
| if len(value) == 0 { |
| sliceVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("auto %s = %s();\n", |
| sliceVar, typeName(decl))) |
| b.lastVar = sliceVar |
| return |
| |
| } |
| var elements []string |
| elemDecl, _ := decl.Elem() |
| for _, item := range value { |
| gidlmixer.Visit(b, item, elemDecl) |
| elements = append(elements, fmt.Sprintf("std::move(%s)", b.lastVar)) |
| } |
| arrayVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("auto %s = fidl::Array<%s, %d>{%s};\n", |
| arrayVar, elemName(decl), len(elements), strings.Join(elements, ", "))) |
| sliceVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("auto %s = %s(%s.data(), %d);\n", |
| sliceVar, typeName(decl), arrayVar, len(elements))) |
| b.lastVar = sliceVar |
| } |
| |
| func (b *llcppValueBuilder) OnNull(decl gidlmixer.Declaration) { |
| newVar := b.newVar() |
| b.Builder.WriteString(fmt.Sprintf("%s %s{};\n", typeName(decl), newVar)) |
| b.lastVar = newVar |
| } |
| |
| func typeNameImpl(decl gidlmixer.Declaration, ignoreNullable bool) string { |
| switch decl := decl.(type) { |
| case *gidlmixer.BoolDecl: |
| return "bool" |
| case *gidlmixer.NumberDecl: |
| return numberName(decl.Typ) |
| case *gidlmixer.StringDecl: |
| return "fidl::StringView" |
| case *gidlmixer.StructDecl: |
| if !ignoreNullable && decl.IsNullable() { |
| return fmt.Sprintf("fidl::tracking_ptr<%s>", identifierName(decl.Name)) |
| } |
| return identifierName(decl.Name) |
| case *gidlmixer.TableDecl: |
| return identifierName(decl.Name) |
| case *gidlmixer.XUnionDecl: |
| return identifierName(decl.Name) |
| case *gidlmixer.ArrayDecl: |
| return fmt.Sprintf("fidl::Array<%s, %d>", elemName(decl), decl.Size()) |
| case *gidlmixer.VectorDecl: |
| return fmt.Sprintf("fidl::VectorView<%s>", elemName(decl)) |
| default: |
| panic("unhandled case") |
| } |
| } |
| |
| func typeName(decl gidlmixer.Declaration) string { |
| return typeNameImpl(decl, false) |
| } |
| |
| func typeNameIgnoreNullable(decl gidlmixer.Declaration) string { |
| return typeNameImpl(decl, true) |
| } |
| |
| func identifierName(eci fidlir.EncodedCompoundIdentifier) string { |
| parts := strings.Split(string(eci), "/") |
| if parts[0] == "conformance" { |
| parts = append([]string{"llcpp"}, parts...) |
| } |
| return strings.Join(parts, "::") |
| } |
| |
| func elemName(parent gidlmixer.ListDeclaration) string { |
| if elemDecl, ok := parent.Elem(); ok { |
| return typeName(elemDecl) |
| } |
| panic("missing element") |
| } |
| |
| func numberName(primitiveSubtype fidlir.PrimitiveSubtype) string { |
| return fmt.Sprintf("%s_t", primitiveSubtype) |
| } |
| |
| func llcppType(gidlTypeString string) string { |
| return "llcpp::conformance::" + gidlTypeString |
| } |
| |
| func llcppErrorCode(code gidlir.ErrorCode) string { |
| // TODO(fxb/35381) Implement different codes for different FIDL error cases. |
| return "ZX_ERR_INVALID_ARGS" |
| } |