| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package analysis |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "log" |
| "os/exec" |
| "strconv" |
| "strings" |
| |
| "fidl-lsp/third_party/fidlgen" |
| |
| "fidl-lsp/state" |
| ) |
| |
| type compileResult struct { |
| lib *Library |
| diags map[state.FileID][]Diagnostic |
| } |
| |
| // Compile the FIDL library that includes the file at `path`. |
| // Returns either a compileResult containing the name of the library along with |
| // any diagnostics returned by fidlc and fidl-lint, or an error if compilation |
| // fails. |
| func (a *Analyzer) compile(fs *state.FileSystem, path state.FileID) (compileResult, error) { |
| var jsonPath string |
| file, err := fs.File(path) |
| if err != nil { |
| return compileResult{}, fmt.Errorf("could not find file `%s`", path) |
| } |
| |
| libraryName, err := state.LibraryOfFile(file) |
| if err != nil { |
| return compileResult{}, fmt.Errorf("could not find library name of file `%s`: %s", path, err) |
| } |
| jsonPath = a.pathToJSON(libraryName) |
| |
| args := append([]string{"--format=json"}, "--json", jsonPath) |
| for _, experiment := range a.cfg.FidlExperiments { |
| args = append(args, "--experimental", experiment) |
| } |
| files, err := a.FindDeps(fs, path) |
| if err != nil { |
| return compileResult{}, fmt.Errorf("could not find dependencies of file `%s`: %s", path, err) |
| } |
| args = append(args, files...) |
| |
| cmd := exec.Command(a.cfg.FidlcPath, args...) |
| var stderr bytes.Buffer |
| cmd.Stderr = &stderr |
| if err := cmd.Run(); err == nil { |
| // If fidlc compiled successfully, update a.libs with the compiled JSON IR |
| if err := a.importLibraryWithFile(jsonPath, path); err != nil { |
| return compileResult{}, fmt.Errorf("error on adding compiled JSON: %s", err) |
| } |
| } else { |
| // TODO: there is no platform-independent way in Go to check an error |
| // status code, but if we can, we should somehow determine if fidlc |
| // exited with status code 1 (failure due to FIDL errors) or something |
| // else (internal error or command otherwise failed). |
| // If it's status code 1, we should continue, but anything else, we |
| // should return an error from `compile` to signal failure. |
| } |
| |
| diags := make(map[state.FileID][]Diagnostic) |
| lib, ok := a.getLibraryWithFile(libraryName, path) |
| if !ok { |
| log.Printf("could not find library `%s` with file `%s`\n", libraryName.FullyQualifiedName(), path) |
| } else { |
| for fileName := range lib.files { |
| fileID, err := a.inputFileToFileID(fileName) |
| if err == nil { |
| diags[fileID] = []Diagnostic{} |
| } |
| } |
| } |
| if errorsAndWarnings, err := a.fidlcDiagsFromStderr(stderr.Bytes()); err == nil { |
| for fileID, fileDiags := range errorsAndWarnings { |
| if _, ok := diags[fileID]; !ok { |
| diags[fileID] = []Diagnostic{} |
| } |
| diags[fileID] = append(diags[fileID], fileDiags...) |
| } |
| } |
| if lints, err := a.runFidlLint(a.inputFilesFIDL[path]); err == nil { |
| for fileID, fileDiags := range lints { |
| if _, ok := diags[fileID]; !ok { |
| diags[fileID] = []Diagnostic{} |
| } |
| diags[fileID] = append(diags[fileID], fileDiags...) |
| } |
| } |
| |
| return compileResult{ |
| lib: lib, |
| diags: diags, |
| }, nil |
| } |
| |
| // compileLibrary essentially does the same thing as importLibrary but for a |
| // library that is already stored in a.libs, but the JSON IR for which has not |
| // been imported. |
| func (a *Analyzer) compileLibrary(lib *Library) error { |
| data, err := ioutil.ReadFile(lib.json) |
| if err != nil { |
| return err |
| } |
| var jsonIR FidlLibrary |
| if err := json.Unmarshal(data, &jsonIR); err != nil { |
| return err |
| } |
| lib.ir = &jsonIR |
| |
| // Construct symbol map from JSON IR for the file's library |
| symbols, err := a.genSymbolMap(jsonIR) |
| if err != nil { |
| return fmt.Errorf("unable to generate symbol map from library %s: %s", jsonIR.Name, err) |
| } |
| lib.symbols = symbols |
| |
| return nil |
| } |
| |
| // importLibraryWithFile reads in the JSON IR at absolute filepath `jsonPath`. |
| func (a *Analyzer) importLibraryWithFile(jsonPath string, path state.FileID) error { |
| data, err := ioutil.ReadFile(jsonPath) |
| if err != nil { |
| return err |
| } |
| var jsonIR FidlLibrary |
| if err := json.Unmarshal(data, &jsonIR); err != nil { |
| return err |
| } |
| // TODO: import more things? files + deps? |
| libName := fidlgen.MustReadLibraryName(jsonIR.Name) |
| lib, ok := a.getLibraryWithFile(libName, path) |
| if !ok { |
| lib = &Library{} |
| a.libs = append(a.libs, lib) |
| } |
| lib.ir = &jsonIR |
| lib.json = jsonPath |
| |
| // Construct symbol map from JSON IR for the file's library |
| symbols, err := a.genSymbolMap(jsonIR) |
| if err != nil { |
| return fmt.Errorf("unable to generate symbol map from library %s: %s", jsonIR.Name, err) |
| } |
| lib.symbols = symbols |
| |
| return nil |
| } |
| |
| type symbolMap map[string]*symbolInfo |
| |
| func (a *Analyzer) genSymbolMap(l FidlLibrary) (symbolMap, error) { |
| sm := make(symbolMap) |
| |
| for _, d := range l.BitsDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| FromTypeAlias: d.FromTypeAlias.TypeAlias(), |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: BitsKind, |
| Bits: &BitsTypeInfo{ |
| Type: d.Type.Type(), |
| }, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| a.addMembersToSymbolMap(sm, d.Name, d.Members) |
| } |
| for _, d := range l.ConstDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| FromTypeAlias: d.FromTypeAlias.TypeAlias(), |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: ConstKind, |
| Const: &ConstTypeInfo{ |
| Type: d.Type.Type(), |
| Value: d.Value.Value, |
| }, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| } |
| for _, d := range l.EnumDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| FromTypeAlias: d.FromTypeAlias.TypeAlias(), |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: EnumKind, |
| Enum: &EnumTypeInfo{ |
| Type: PrimitiveSubtype(d.Type), |
| }, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| a.addMembersToSymbolMap(sm, d.Name, d.Members) |
| } |
| for _, d := range l.ProtocolDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: ProtocolKind, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| for _, m := range d.Methods { |
| methodName := fmt.Sprintf("%s.%s", d.Name, m.Name) |
| sm[methodName] = &symbolInfo{ |
| name: m.Name, |
| definition: a.fidlLocToStateLoc(m.Loc), |
| typeInfo: Type{ |
| IsMethod: true, |
| Attrs: m.Attrs, |
| }, |
| } |
| if len(m.MaybeRequest) > 0 { |
| a.addMembersToSymbolMap(sm, methodName, m.MaybeRequest) |
| } |
| if len(m.MaybeResponse) > 0 { |
| a.addMembersToSymbolMap(sm, methodName, m.MaybeResponse) |
| } |
| } |
| } |
| for _, d := range l.ServiceDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: ServiceKind, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| a.addMembersToSymbolMap(sm, d.Name, d.Members) |
| } |
| for _, d := range l.StructDecls { |
| // Skip anonymous structs, since we add them to the symbol map as method |
| // parameter structs |
| if d.Anonymous { |
| continue |
| } |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: StructKind, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| a.addMembersToSymbolMap(sm, d.Name, d.Members) |
| } |
| for _, d := range l.TableDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: TableKind, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| a.addMembersToSymbolMap(sm, d.Name, d.Members) |
| } |
| for _, d := range l.UnionDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: UnionKind, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| a.addMembersToSymbolMap(sm, d.Name, d.Members) |
| } |
| for _, d := range l.TypeAliasDecls { |
| sm[d.Name] = &symbolInfo{ |
| name: d.Name, |
| definition: a.fidlLocToStateLoc(d.Loc), |
| typeInfo: Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| IsDecl: true, |
| Name: d.Name, |
| Kind: TypeAliasKind, |
| TypeAlias: &TypeAliasTypeInfo{ |
| Type: d.TypeCtor.Type(), |
| }, |
| }, |
| Attrs: d.Attrs, |
| }, |
| } |
| } |
| |
| return sm, nil |
| } |
| |
| func (a *Analyzer) addMembersToSymbolMap(sm symbolMap, declName string, members []member) { |
| for _, m := range members { |
| memberName := fmt.Sprintf("%s.%s", declName, m.Name) |
| sm[memberName] = &symbolInfo{ |
| name: m.Name, |
| definition: a.fidlLocToStateLoc(m.Loc), |
| typeInfo: m.Type.Type(), |
| } |
| sm[memberName].typeInfo.FromTypeAlias = m.FromTypeAlias.TypeAlias() |
| sm[memberName].typeInfo.Attrs = m.Attrs |
| } |
| } |
| |
| func (a *Analyzer) fidlLocToStateLoc(loc location) state.Location { |
| // If this JSON IR was compiled by the language server, the `Filename`s for |
| // for all the symbols will be tmp input files from the Analyzer. We want |
| // to point at the corresponding editor files for these tmp files. |
| fileID, err := a.inputFileToFileID(loc.Filename) |
| if err != nil { |
| // We assume `Filename` is a filepath to a precompiled JSON IR, provided |
| // in the CompiledLibraries on startup. |
| // |
| // The `Filename` in a decl's location is recorded relative to where |
| // fidlc was invoked. All the locations in CompiledLibraries are |
| // prepended with "../../" which can be replaced by the `BuildRootDir`. |
| fileID = state.FileID(strings.Replace(loc.Filename, "../..", a.cfg.BuildRootDir, 1)) |
| } |
| return state.Location{ |
| FileID: fileID, |
| Range: state.Range{ |
| Start: state.Position{Line: loc.Line - 1, Character: loc.Column - 1}, |
| End: state.Position{Line: loc.Line - 1, Character: loc.Column - 1 + loc.Length}, |
| }, |
| } |
| } |
| |
| func (d declType) Type() Type { |
| t := Type{Kind: d.Kind} |
| |
| switch d.Kind { |
| default: |
| // Sometimes this will be called with a zeroed declType, e.g. for bits |
| // and enum members, which don't have types. |
| return t |
| case ArrayType: |
| t.Array = &ArrayTypeInfo{ |
| ElementType: d.ElementType.Type(), |
| ElementCount: d.ElementCount, |
| } |
| case VectorType: |
| t.Vector = &VectorTypeInfo{ |
| ElementType: d.ElementType.Type(), |
| ElementCount: d.MaybeElementCount, |
| Nullable: d.Nullable, |
| } |
| case StringType: |
| t.String = &StringTypeInfo{ |
| ElementCount: d.MaybeElementCount, |
| Nullable: d.Nullable, |
| } |
| case HandleType: |
| t.Handle = &HandleTypeInfo{ |
| Subtype: HandleSubtype(d.Subtype), |
| Rights: d.Rights, |
| Nullable: d.Nullable, |
| } |
| case RequestType: |
| t.Request = &RequestTypeInfo{ |
| Subtype: d.Subtype, |
| Nullable: d.Nullable, |
| } |
| case PrimitiveType: |
| t.Primitive = &PrimitiveTypeInfo{ |
| Subtype: PrimitiveSubtype(d.Subtype), |
| } |
| case IdentifierType: |
| t.Identifier = &IdentifierTypeInfo{ |
| Identifier: d.Identifier, |
| Nullable: d.Nullable, |
| } |
| } |
| |
| return t |
| } |
| |
| func (t typeCtor) Type() Type { |
| switch t.Name { |
| case string(ArrayType): |
| ty := Type{ |
| Kind: ArrayType, |
| Array: &ArrayTypeInfo{}, |
| } |
| if len(t.Args) > 0 { |
| ty.Array.ElementType = t.Args[0].Type() |
| } |
| if t.Size != nil { |
| if count, err := strconv.ParseUint(t.Size.Value, 10, 32); err == nil { |
| ty.Array.ElementCount = uint(count) |
| } |
| } |
| return ty |
| |
| case string(VectorType): |
| ty := Type{ |
| Kind: VectorType, |
| Vector: &VectorTypeInfo{ |
| Nullable: t.Nullable, |
| }, |
| } |
| if len(t.Args) > 0 { |
| ty.Vector.ElementType = t.Args[0].Type() |
| } |
| if t.Size != nil { |
| if count, err := strconv.ParseUint(t.Size.Value, 10, 32); err == nil { |
| countUint := uint(count) |
| ty.Vector.ElementCount = &countUint |
| } |
| } |
| return ty |
| |
| case string(StringType): |
| ty := Type{ |
| Kind: StringType, |
| String: &StringTypeInfo{ |
| Nullable: t.Nullable, |
| }, |
| } |
| if t.Size != nil { |
| if count, err := strconv.ParseUint(t.Size.Value, 10, 32); err == nil { |
| countUint := uint(count) |
| ty.String.ElementCount = &countUint |
| } |
| } |
| return ty |
| |
| case string(HandleType): |
| ty := Type{ |
| Kind: HandleType, |
| Handle: &HandleTypeInfo{ |
| Nullable: t.Nullable, |
| }, |
| } |
| if t.HandleSubtype != nil { |
| ty.Handle.Subtype = *t.HandleSubtype |
| } |
| return ty |
| |
| case string(RequestType): |
| ty := Type{ |
| Kind: RequestType, |
| Request: &RequestTypeInfo{ |
| Nullable: t.Nullable, |
| }, |
| } |
| if len(t.Args) > 0 { |
| ty.Request.Subtype = t.Args[0].Name |
| } |
| return ty |
| |
| case string(Bool), Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64, Float32, Float64: |
| return Type{ |
| Kind: PrimitiveType, |
| Primitive: &PrimitiveTypeInfo{ |
| Subtype: PrimitiveSubtype(t.Name), |
| }, |
| } |
| |
| default: |
| // We assume it is the name of an identifier type. |
| return Type{ |
| Kind: IdentifierType, |
| Identifier: &IdentifierTypeInfo{ |
| Identifier: t.Name, |
| Nullable: t.Nullable, |
| }, |
| } |
| } |
| } |
| |
| func (t typeCtor) TypeAlias() *string { |
| if t.Name == "" { |
| return nil |
| } |
| return &t.Name |
| } |