diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 41024c5d3a..2bb056960e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -36,7 +36,7 @@ jobs: make cmd/ansibleserver cmd/cloudnet cmd/notify make cmd/host-deployer && ./scripts/bundle_libraries.sh _output/bin/bundles/host-deployer _output/bin/host-deployer make cmd/baremetal-agent && ./scripts/bundle_libraries.sh _output/bin/bundles/baremetal-agent _output/bin/baremetal-agent - make cmd/cloudevent cmd/devtool + make cmd/cloudevent cmd/devtool cmd/cloudid make cmd/*cli make cmd/esxi-agent make docker-alpine-build F='-j4 cmd/host cmd/vpcagent cmd/region-dns' @@ -211,6 +211,16 @@ jobs: snapshot: true dockerfile: build/docker/Dockerfile.cloudevent + - name: Image cloudid + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: registry.cn-beijing.aliyuncs.com/yunionio/cloudid + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + registry: registry.cn-beijing.aliyuncs.com + snapshot: true + dockerfile: build/docker/Dockerfile.cloudid + - name: Image devtool uses: elgohr/Publish-Docker-Github-Action@master with: diff --git a/build/cloudid/vars b/build/cloudid/vars new file mode 100644 index 0000000000..91183a1bcb --- /dev/null +++ b/build/cloudid/vars @@ -0,0 +1 @@ +DESCRIPTION="Yunion CloudId" diff --git a/build/docker/Dockerfile.cloudid b/build/docker/Dockerfile.cloudid new file mode 100644 index 0000000000..33426dbdd4 --- /dev/null +++ b/build/docker/Dockerfile.cloudid @@ -0,0 +1,3 @@ +FROM registry.cn-beijing.aliyuncs.com/yunionio/onecloud-base:latest + +ADD ./_output/bin/cloudid /opt/yunion/bin/cloudid diff --git a/cmd/climc/main.go b/cmd/climc/main.go index ca999492f4..6711336e78 100644 --- a/cmd/climc/main.go +++ b/cmd/climc/main.go @@ -19,6 +19,7 @@ import ( _ "yunion.io/x/onecloud/cmd/climc/shell" _ "yunion.io/x/onecloud/cmd/climc/shell/ansible" _ "yunion.io/x/onecloud/cmd/climc/shell/cloudevent" + _ "yunion.io/x/onecloud/cmd/climc/shell/cloudid" _ "yunion.io/x/onecloud/cmd/climc/shell/cloudnet" _ "yunion.io/x/onecloud/cmd/climc/shell/compute" _ "yunion.io/x/onecloud/cmd/climc/shell/etcd" diff --git a/cmd/climc/shell/cloudid/cloudgroup.go b/cmd/climc/shell/cloudid/cloudgroup.go new file mode 100644 index 0000000000..232250f10c --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudgroup.go @@ -0,0 +1,167 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type CloudgroupListOptions struct { + options.BaseListOptions + + ClouduserId string `json:"clouduser_id"` + CloudpolicyId string `json:"cloudpolicy_id"` + } + R(&CloudgroupListOptions{}, "cloud-group-list", "List cloud groups", func(s *mcclient.ClientSession, opts *CloudgroupListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.Cloudgroups.List(s, params) + if err != nil { + return err + } + printList(result, modules.Cloudgroups.GetColumns(s)) + return nil + }) + + type CloudgroupCreateOptions struct { + NAME string `json:"name"` + PROVIDER string `json:"provider" choices:"Google|Aliyun|Aws|Huawei|Qcloud"` + CloudpolicyIds []string `json:"cloudpolicy_ids"` + Desc string `json:"description"` + } + + R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create cloud group", func(s *mcclient.ClientSession, opts *CloudgroupCreateOptions) error { + params := jsonutils.Marshal(opts) + result, err := modules.Cloudgroups.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type CloudgroupIdOptions struct { + ID string `help:"Cloudgroup Id"` + } + + R(&CloudgroupIdOptions{}, "cloud-group-delete", "Delete cloud group", func(s *mcclient.ClientSession, opts *CloudgroupIdOptions) error { + result, err := modules.Cloudgroups.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudgroupIdOptions{}, "cloud-group-show", "Show cloud group", func(s *mcclient.ClientSession, opts *CloudgroupIdOptions) error { + result, err := modules.Cloudgroups.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudgroupIdOptions{}, "cloud-group-syncstatus", "Sync cloud group status", func(s *mcclient.ClientSession, opts *CloudgroupIdOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "syncstatus", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type CloudgroupPolicyOptions struct { + ID string `help:"Cloudgroup Id"` + CLOUDPOLICY_ID string `help:"Cloudpolicy Id"` + } + + R(&CloudgroupPolicyOptions{}, "cloud-group-attach-policy", "Attach policy for cloud group", func(s *mcclient.ClientSession, opts *CloudgroupPolicyOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "attach-policy", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudgroupPolicyOptions{}, "cloud-group-detach-policy", "Detach policy from cloud group", func(s *mcclient.ClientSession, opts *CloudgroupPolicyOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "detach-policy", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type CloudgroupUserOptions struct { + ID string `help:"Cloudgroup Id"` + CLOUDUSER_ID string `help:"Clouduser Id"` + } + + R(&CloudgroupUserOptions{}, "cloud-group-add-user", "Add user to cloud group", func(s *mcclient.ClientSession, opts *CloudgroupUserOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "add-user", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudgroupUserOptions{}, "cloud-group-remove-user", "Remove user from cloud group", func(s *mcclient.ClientSession, opts *CloudgroupUserOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "remove-user", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type CloudgroupPoliciesOptions struct { + ID string `help:"Cloudgroup Id"` + CloudpolicyIds []string `json:"cloudpolicy_ids"` + } + + R(&CloudgroupPoliciesOptions{}, "cloud-group-set-policies", "Set cloudpolicies for cloud group", func(s *mcclient.ClientSession, opts *CloudgroupPoliciesOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "set-policies", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type CloudgroupUsersOptions struct { + ID string `help:"Cloudgroup Id"` + ClouduserIds []string `json:"clouduser_ids"` + } + + R(&CloudgroupUsersOptions{}, "cloud-group-set-users", "Set users for cloud group", func(s *mcclient.ClientSession, opts *CloudgroupUsersOptions) error { + result, err := modules.Cloudgroups.PerformAction(s, opts.ID, "set-users", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/cloudgroupcache.go b/cmd/climc/shell/cloudid/cloudgroupcache.go new file mode 100644 index 0000000000..3323d9e07e --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudgroupcache.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type CloudgroupcacheListOptions struct { + options.BaseListOptions + + CloudgroupId string `json:"cloudgroup_id"` + } + R(&CloudgroupcacheListOptions{}, "cloud-group-cache-list", "List cloud group caches", func(s *mcclient.ClientSession, opts *CloudgroupcacheListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.Cloudgroupcaches.List(s, params) + if err != nil { + return err + } + printList(result, modules.Cloudgroupcaches.GetColumns(s)) + return nil + }) + + type CloudgroupcacheIdOption struct { + ID string `help:"Cloudgroup Id"` + } + + R(&CloudgroupcacheIdOption{}, "cloud-group-cache-show", "Show cloud groupcache details", func(s *mcclient.ClientSession, opts *CloudgroupcacheIdOption) error { + result, err := modules.Cloudgroupcaches.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudgroupcacheIdOption{}, "cloud-group-cache-syncstatus", "Sync cloudgroupcache", func(s *mcclient.ClientSession, opts *CloudgroupcacheIdOption) error { + result, err := modules.Cloudgroupcaches.PerformAction(s, opts.ID, "syncstatus", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudgroupcacheIdOption{}, "cloud-group-cache-delete", "Delete cloudgroupcache", func(s *mcclient.ClientSession, opts *CloudgroupcacheIdOption) error { + result, err := modules.Cloudgroupcaches.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/cloudgrouppolicies.go b/cmd/climc/shell/cloudid/cloudgrouppolicies.go new file mode 100644 index 0000000000..aea89de86b --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudgrouppolicies.go @@ -0,0 +1,72 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type CloudgroupPolicyListOptions struct { + options.BaseListOptions + Cloudgroup string `help:"ID or Name of Cloudgroup"` + Cloudpolicy string `help:"Policy ID or name"` + } + R(&CloudgroupPolicyListOptions{}, "cloud-group-policy-list", "List cloudgroup cloudpolicy pairs", func(s *mcclient.ClientSession, args *CloudgroupPolicyListOptions) error { + var params *jsonutils.JSONDict + { + var err error + params, err = args.BaseListOptions.Params() + if err != nil { + return err + + } + } + var result *modulebase.ListResult + var err error + if len(args.Cloudgroup) > 0 { + result, err = modules.Cloudgrouppolicies.ListDescendent(s, args.Cloudgroup, params) + } else if len(args.Cloudpolicy) > 0 { + result, err = modules.Cloudgrouppolicies.ListDescendent2(s, args.Cloudpolicy, params) + } else { + result, err = modules.Cloudgrouppolicies.List(s, params) + } + if err != nil { + return err + } + printList(result, modules.Cloudgrouppolicies.GetColumns(s)) + return nil + }) + + type CloudgroupPolicyDetailOptions struct { + CLOUDUSER string `help:"ID or Name of Cloudgroup"` + CLOUDPOLICY string `help:"ID or Name of Cloudpolicy"` + } + R(&CloudgroupPolicyDetailOptions{}, "cloud-group-policy-show", "Show cloudgrouppolicy details", func(s *mcclient.ClientSession, args *CloudgroupPolicyDetailOptions) error { + query := jsonutils.NewDict() + result, err := modules.Cloudgrouppolicies.Get(s, args.CLOUDUSER, args.CLOUDPOLICY, query) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/cloudgroupusers.go b/cmd/climc/shell/cloudid/cloudgroupusers.go new file mode 100644 index 0000000000..30d141789e --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudgroupusers.go @@ -0,0 +1,72 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type CloudgroupUserListOptions struct { + options.BaseListOptions + Cloudgroup string `help:"ID or Name of Cloudgroup"` + Clouduser string `help:"User ID or name"` + } + R(&CloudgroupUserListOptions{}, "cloud-group-user-list", "List cloudgroup clouduser pairs", func(s *mcclient.ClientSession, args *CloudgroupUserListOptions) error { + var params *jsonutils.JSONDict + { + var err error + params, err = args.BaseListOptions.Params() + if err != nil { + return err + + } + } + var result *modulebase.ListResult + var err error + if len(args.Cloudgroup) > 0 { + result, err = modules.Cloudgroupusers.ListDescendent(s, args.Cloudgroup, params) + } else if len(args.Clouduser) > 0 { + result, err = modules.Cloudgroupusers.ListDescendent2(s, args.Clouduser, params) + } else { + result, err = modules.Cloudgroupusers.List(s, params) + } + if err != nil { + return err + } + printList(result, modules.Cloudgroupusers.GetColumns(s)) + return nil + }) + + type CloudgroupUserDetailOptions struct { + CLOUDUSER string `help:"ID or Name of Cloudgroup"` + CLOUDPOLICY string `help:"ID or Name of Clouduser"` + } + R(&CloudgroupUserDetailOptions{}, "cloud-group-user-show", "Show cloudgroupuser details", func(s *mcclient.ClientSession, args *CloudgroupUserDetailOptions) error { + query := jsonutils.NewDict() + result, err := modules.Cloudgroupusers.Get(s, args.CLOUDUSER, args.CLOUDPOLICY, query) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/cloudpolicy.go b/cmd/climc/shell/cloudid/cloudpolicy.go new file mode 100644 index 0000000000..149036d33e --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudpolicy.go @@ -0,0 +1,81 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type CloudpolicyListOptions struct { + options.BaseListOptions + + ClouduserId string `json:"clouduser_id"` + CloudgroupId string `json:"cloudgroup_id"` + } + R(&CloudpolicyListOptions{}, "cloud-policy-list", "List cloud policies", func(s *mcclient.ClientSession, opts *CloudpolicyListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.Cloudpolicies.List(s, params) + if err != nil { + return err + } + printList(result, modules.Cloudpolicies.GetColumns(s)) + return nil + }) + + type CloudpolicyIdOptions struct { + ID string `help:"Cloudpolicy Id"` + } + + R(&CloudpolicyIdOptions{}, "cloud-policy-show", "Sow cloud policiy details", func(s *mcclient.ClientSession, opts *CloudpolicyIdOptions) error { + result, err := modules.Cloudpolicies.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type CloudpolicyGroupOptions struct { + ID string `help:"Cloudpolicy Id"` + CLOUDGROUP_ID string `help:"Cloudgroup Id" json:"cloudgroup_id"` + } + + R(&CloudpolicyGroupOptions{}, "cloud-policy-assign-group", "Assign cloud policiy to group", func(s *mcclient.ClientSession, opts *CloudpolicyGroupOptions) error { + result, err := modules.Cloudpolicies.PerformAction(s, opts.ID, "assign-group", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&CloudpolicyGroupOptions{}, "cloud-policy-revoke-group", "Revoke cloud policiy from group", func(s *mcclient.ClientSession, opts *CloudpolicyGroupOptions) error { + result, err := modules.Cloudpolicies.PerformAction(s, opts.ID, "revoke-group", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/clouduserpolicies.go b/cmd/climc/shell/cloudid/clouduserpolicies.go new file mode 100644 index 0000000000..c7a98f7acf --- /dev/null +++ b/cmd/climc/shell/cloudid/clouduserpolicies.go @@ -0,0 +1,72 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type ClouduserPolicyListOptions struct { + options.BaseListOptions + Clouduser string `help:"ID or Name of Clouduser"` + Cloudpolicy string `help:"Policy ID or name"` + } + R(&ClouduserPolicyListOptions{}, "cloud-user-policy-list", "List clouduser cloudpolicy pairs", func(s *mcclient.ClientSession, args *ClouduserPolicyListOptions) error { + var params *jsonutils.JSONDict + { + var err error + params, err = args.BaseListOptions.Params() + if err != nil { + return err + + } + } + var result *modulebase.ListResult + var err error + if len(args.Clouduser) > 0 { + result, err = modules.Clouduserpolicies.ListDescendent(s, args.Clouduser, params) + } else if len(args.Cloudpolicy) > 0 { + result, err = modules.Clouduserpolicies.ListDescendent2(s, args.Cloudpolicy, params) + } else { + result, err = modules.Clouduserpolicies.List(s, params) + } + if err != nil { + return err + } + printList(result, modules.Clouduserpolicies.GetColumns(s)) + return nil + }) + + type ClouduserPolicyDetailOptions struct { + CLOUDUSER string `help:"ID or Name of Clouduser"` + CLOUDPOLICY string `help:"ID or Name of Cloudpolicy"` + } + R(&ClouduserPolicyDetailOptions{}, "cloud-user-policy-show", "Show clouduserpolicy details", func(s *mcclient.ClientSession, args *ClouduserPolicyDetailOptions) error { + query := jsonutils.NewDict() + result, err := modules.Clouduserpolicies.Get(s, args.CLOUDUSER, args.CLOUDPOLICY, query) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/cloudusers.go b/cmd/climc/shell/cloudid/cloudusers.go new file mode 100644 index 0000000000..b6ddda3311 --- /dev/null +++ b/cmd/climc/shell/cloudid/cloudusers.go @@ -0,0 +1,197 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type ClouduserListOptions struct { + options.BaseListOptions + CloudaccountId string `help:"Cloudaccount Id"` + CloudproviderId string `help:"Cloudprovider Id"` + CloudpolicyId string `help:"filter cloudusers by cloudpolicy"` + CloudgroupId string `help:"filter cloudusers by cloudgroup"` + } + R(&ClouduserListOptions{}, "cloud-user-list", "List cloud users", func(s *mcclient.ClientSession, opts *ClouduserListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.Cloudusers.List(s, params) + if err != nil { + return err + } + printList(result, modules.Cloudusers.GetColumns(s)) + return nil + }) + + type ClouduserCreateOptions struct { + Name string + CloudaccountId string `help:"Cloudaccount Id"` + CloudproviderId string `help:"Cloudprovider Id"` + OwnerId string `help:"Owner Id"` + CloudpolicyIds []string `help:"cloudpolicy ids"` + CloudgroupIds []string `help:"cloudgroup ids"` + Email string `help:"email address"` + MobilePhone string `help:"phone number"` + IsConsoleLogin *bool `help:"is console login"` + Password string `help:"clouduser password"` + } + + R(&ClouduserCreateOptions{}, "cloud-user-create", "Create cloud user", func(s *mcclient.ClientSession, opts *ClouduserCreateOptions) error { + params := jsonutils.Marshal(opts) + result, err := modules.Cloudusers.Create(s, params) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ClouduserIdOptions struct { + ID string `help:"Clouduser Id"` + } + + R(&ClouduserIdOptions{}, "cloud-user-delete", "Delete cloud user", func(s *mcclient.ClientSession, opts *ClouduserIdOptions) error { + result, err := modules.Cloudusers.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&ClouduserIdOptions{}, "cloud-user-logininfo", "Show cloud user login info", func(s *mcclient.ClientSession, opts *ClouduserIdOptions) error { + result, err := modules.Cloudusers.GetLoginInfo(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&ClouduserIdOptions{}, "cloud-user-show", "Show cloud user", func(s *mcclient.ClientSession, opts *ClouduserIdOptions) error { + result, err := modules.Cloudusers.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ClouduserSyncOptions struct { + ID string `help:"Clouduser Id"` + PolicyOnly bool `help:"Ony sync clouduser policies for cloud"` + } + + R(&ClouduserSyncOptions{}, "cloud-user-sync", "Sync cloud user policies", func(s *mcclient.ClientSession, opts *ClouduserSyncOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "sync", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&ClouduserIdOptions{}, "cloud-user-syncstatus", "Sync cloud user status", func(s *mcclient.ClientSession, opts *ClouduserIdOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "syncstatus", nil) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ClouduserPolicyOptions struct { + ID string `help:"Clouduser Id"` + CLOUDPOLICY_ID string `help:"cloudpolicy Id"` + } + + R(&ClouduserPolicyOptions{}, "cloud-user-attach-policy", "Attach policy for cloud user", func(s *mcclient.ClientSession, opts *ClouduserPolicyOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "attach-policy", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&ClouduserPolicyOptions{}, "cloud-user-detach-policy", "Detach policy from cloud user", func(s *mcclient.ClientSession, opts *ClouduserPolicyOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "detach-policy", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ClouduserPasswordOptions struct { + ID string `help:"Clouduser Id"` + Password string `help:"clouduser password"` + } + + R(&ClouduserPasswordOptions{}, "cloud-user-reset-password", "Reset clouduser password", func(s *mcclient.ClientSession, opts *ClouduserPasswordOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "reset-password", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ClouduserChangeOwnerOptions struct { + ID string `help:"clouduser id"` + USER_ID string `help:"local user id"` + } + + R(&ClouduserChangeOwnerOptions{}, "cloud-user-change-owner", "Change clouduser owner", func(s *mcclient.ClientSession, opts *ClouduserChangeOwnerOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "change-owner", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type ClouduserGroupOptions struct { + ID string `help:"clouduser id"` + CLOUDGROUP_ID string `help:"cloudgroup id" json:"cloudgroup_id"` + } + + R(&ClouduserGroupOptions{}, "cloud-user-join-group", "Join user to cloudgroup", func(s *mcclient.ClientSession, opts *ClouduserGroupOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "join-group", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + R(&ClouduserGroupOptions{}, "cloud-user-leave-group", "Leave from cloudgroup", func(s *mcclient.ClientSession, opts *ClouduserGroupOptions) error { + result, err := modules.Cloudusers.PerformAction(s, opts.ID, "leave-group", jsonutils.Marshal(opts)) + if err != nil { + return err + } + printObject(result) + return nil + }) + +} diff --git a/cmd/climc/shell/cloudid/common.go b/cmd/climc/shell/cloudid/common.go new file mode 100644 index 0000000000..4e6a42505d --- /dev/null +++ b/cmd/climc/shell/cloudid/common.go @@ -0,0 +1,26 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/util/printutils" +) + +var ( + R = shell.R + printList = printutils.PrintJSONList + printObject = printutils.PrintJSONObject +) diff --git a/cmd/climc/shell/misc/projectresources.go b/cmd/climc/shell/misc/scoperesources.go similarity index 75% rename from cmd/climc/shell/misc/projectresources.go rename to cmd/climc/shell/misc/scoperesources.go index 3b03e797d3..8a7a19d41d 100644 --- a/cmd/climc/shell/misc/projectresources.go +++ b/cmd/climc/shell/misc/scoperesources.go @@ -22,11 +22,11 @@ import ( ) func init() { - type ProjectResourceOptions struct { + type ScopeResourceOptions struct { SERVICE string `help:"Service type"` } - R(&ProjectResourceOptions{}, "project-resource-show", "query backend service for its project resource count", func(s *mcclient.ClientSession, args *ProjectResourceOptions) error { - body, err := modules.GetProjectResources(s, args.SERVICE) + R(&ScopeResourceOptions{}, "scope-resource-show", "query backend service for its scope resource count", func(s *mcclient.ClientSession, args *ScopeResourceOptions) error { + body, err := modules.GetScopeResources(s, args.SERVICE) if err != nil { return err } diff --git a/cmd/cloudid/main.go b/cmd/cloudid/main.go new file mode 100644 index 0000000000..99a848cfde --- /dev/null +++ b/cmd/cloudid/main.go @@ -0,0 +1,23 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "yunion.io/x/onecloud/pkg/cloudid/service" +) + +func main() { + service.StartService() +} diff --git a/go.mod b/go.mod index c919cb6c5d..3810925ee4 100644 --- a/go.mod +++ b/go.mod @@ -108,19 +108,23 @@ require ( go.etcd.io/etcd v0.5.0-alpha.5.0.20191023171146-3cf2f69b5738 go.uber.org/atomic v1.4.0 // indirect golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 - golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 + golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 // indirect golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191008142428-8d021180e987 google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb google.golang.org/grpc v1.26.0 gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/fatih/set.v0 v0.2.1 gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.44.0 // indirect gopkg.in/ldap.v3 v3.0.3 gopkg.in/yaml.v2 v2.2.8 + honnef.co/go/tools v0.0.1-2020.1.4 // indirect k8s.io/api v0.17.3 k8s.io/apimachinery v0.17.3 k8s.io/client-go v9.0.0+incompatible diff --git a/go.sum b/go.sum index 03f8d6da88..15b75df5c1 100644 --- a/go.sum +++ b/go.sum @@ -769,6 +769,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yunionio/go-ceph v0.0.0-20190912101231-6f05a06b3859 h1:wu596gn6sV3j5wy+GDfiG8nwtzIDpalJihfmC/TjoYc= github.com/yunionio/go-ceph v0.0.0-20190912101231-6f05a06b3859/go.mod h1:8XuBae5AzsgotLArJSewMruYVaQs8AlfsK5jBCG8T9Y= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= @@ -836,6 +837,8 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -869,6 +872,8 @@ golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -952,10 +957,14 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4 h1:Toz2IK7k8rbltAXwNAxKcn9OzqyNfMUhUNjz3sL0NMk= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 h1:vmsb6v0zUdmUlXfwKaYrHPPRCV0lHq/IwNIf0ASGjyQ= +golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -1013,8 +1022,12 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY= +gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= @@ -1054,6 +1067,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.0.0-20190918155943-95b840bb6a1f h1:8FRUST8oUkEI45WYKyD8ed7Ad0Kg5v11zHyPkEVb2xo= k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0HRNyQ+8KTEERVsK0PW48= k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= diff --git a/pkg/apis/cloudevent/cloudevent_const.go b/pkg/apis/cloudevent/cloudevent_const.go index 01b30e5d15..cd8911eedb 100644 --- a/pkg/apis/cloudevent/cloudevent_const.go +++ b/pkg/apis/cloudevent/cloudevent_const.go @@ -14,8 +14,10 @@ package cloudevent +import "yunion.io/x/onecloud/pkg/apis" + const ( - SERVICE_TYPE = "cloudevent" + SERVICE_TYPE = apis.SERVICE_TYPE_CLOUDEVENT CLOUD_EVENT_SERVICE_COMPUTE = "compute" CLOUD_EVENT_SERVICE_UNKNOWN = "unknown" diff --git a/pkg/apis/cloudid/cloudaccount.go b/pkg/apis/cloudid/cloudaccount.go new file mode 100644 index 0000000000..7fbf8f7409 --- /dev/null +++ b/pkg/apis/cloudid/cloudaccount.go @@ -0,0 +1,51 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +type CloudaccountResourceListInput struct { + // 根据云账号名称过滤资源 + Cloudaccount string `json:"cloudaccount"` + + // 根据平台过滤 + Provider []string `json:"provider"` + + // swagger:ignore + CloudaccountId string `json:"cloudaccount_id" "yunion:deprecated-by":"cloudaccount"` +} + +type CloudproviderResourceListInput struct { + // 根据云订阅过滤资源 + Cloudprovider string `json:"cloudprovider"` + + // swagger:ignore + CloudproviderId string `json:"cloudprovider_id" "yunion:deprecated-by":"cloudprovider"` +} + +type CloudaccountResourceDetails struct { + // 云账号名称 + Cloudaccount string `json:"cloudaccount"` + // 平台信息 + Provider string `json:"provider"` + // 品牌信息 + Brand string `json:"brand"` + + // 公有云账号登录地址 + IamLoginUrl string `json:"iam_login_url"` +} + +type CloudproviderResourceDetails struct { + // 云订阅名称 + Cloudprovider string `json:"cloudprovider"` +} diff --git a/pkg/apis/cloudid/cloudgroup.go b/pkg/apis/cloudid/cloudgroup.go new file mode 100644 index 0000000000..57c0db493b --- /dev/null +++ b/pkg/apis/cloudid/cloudgroup.go @@ -0,0 +1,214 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + CLOUD_GROUP_STATUS_AVAILABLE = "available" // 可用 + CLOUD_GROUP_STATUS_DELETING = "deleting" // 删除中 + CLOUD_GROUP_STATUS_DELETE_FAILED = "delete_failed" // 删除失败 + CLOUD_GROUP_STATUS_SYNC_POLICIES = "sync_policies" // 同步权限中 + CLOUD_GROUP_STATUS_SYNC_USERS = "sync_users" // 同步用户中 +) + +type CloudgroupJointResourceDetails struct { + apis.JointResourceBaseDetails + + // 公有云用户组名称 + Cloudgroup string `json:"cloudgroup"` +} + +type CloudgroupJointsListInput struct { + apis.JointResourceBaseListInput + + // 根据公有云用户组过滤资源 + Cloudgroup string `json:"cloudgroup"` + // swagger:ignore + CloudgroupId string `json:"cloudgroup_id" "yunion:deprecated-by":"cloudgroup"` +} + +type CloudgroupJointBaseUpdateInput struct { + apis.JointResourceBaseUpdateInput +} + +type CloudgroupUserDetails struct { + CloudgroupJointResourceDetails + SCloudgroup + + ClouduserResourceDetails +} + +type CloudgroupUserListInput struct { + CloudgroupJointsListInput + + ClouduserResourceListInput +} + +type CloudgroupPolicyDetails struct { + CloudgroupJointResourceDetails + + CloudpolicyResourceDetails +} + +type CloudgroupPolicyListInput struct { + CloudgroupJointsListInput + + CloudpolicyResourceListInput +} + +type CloudgroupListInput struct { + apis.StatusInfrasResourceBaseListInput + + // 根据平台过滤 + Provider []string `json:"provider"` + + // 过滤子账号所在的权限组 + ClouduserId string `json:"clouduser_id"` + + // 根据权限过滤权限组 + CloudpolicyId string `json:"cloudpolicy_id"` +} + +type SCloudIdBaseResource struct { + Id string `json:"id"` + Name string `json:"name"` +} + +type CloudgroupDetails struct { + apis.StatusInfrasResourceBaseDetails + SCloudgroup + + // 公有云子用户数量 + ClouduserCount int `json:"clouduser_count"` + // 权限数量 + CloudpolicyCount int `json:"cloudpolicy_count"` + // 公有云权限组缓存数量 + CloudgroupcacheCount int `json:"cloudgroupcache_count"` + + Cloudpolicies []SCloudIdBaseResource `json:"cloudpolicies"` + Cloudusers []SCloudIdBaseResource `json:"cloudusers"` +} + +type CloudgroupCreateInput struct { + apis.StatusInfrasResourceBaseCreateInput + + // 平台 + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 支持 | + // | Qcloud | 支持 | + Provider string `json:"provider"` + + // 权限Id列表, 权限provider必须和权限组provider一致 + CloudpolicyIds []string `json:"cloudpolicy_ids"` +} + +type CloudgroupAddUserInput struct { + + // 用户Id + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 不支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 支持 | + // | 腾讯云 | 支持 | + ClouduserId string `json:"clouduser_id"` +} + +type CloudgroupRemoveUserInput struct { + + // 用户Id + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 不支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 支持 | + // | 腾讯云 | 支持 | + ClouduserId string `json:"clouduser_id"` +} + +type CloudgroupAttachPolicyInput struct { + + // 权限Id + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 不支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 不支持 | + // | 腾讯云 | 支持 | + CloudpolicyId string `json:"cloudpolicy_id"` +} + +type CloudgroupSetUsersInput struct { + + // 公有云子账号Ids + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 支持 | + // | 腾讯云 | 支持 | + ClouduserIds []string `json:"clouduser_ids"` +} + +type CloudgroupSetPoliciesInput struct { + + // 权限Ids + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 不支持 | + // | 腾讯云 | 支持 | + CloudpolicyIds []string `json:"cloudpolicy_ids"` +} + +type CloudgroupDetachPolicyInput struct { + + // 权限Id + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 不支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 不支持 | + // | 腾讯云 | 支持 | + CloudpolicyId string `json:"cloudpolicy_id"` +} + +type CloudgroupSyncstatusInput struct { +} + +type CloudgroupSyncInput struct { +} + +type CloudgroupUpdateInput struct { +} diff --git a/pkg/apis/cloudid/cloudgroupcache.go b/pkg/apis/cloudid/cloudgroupcache.go new file mode 100644 index 0000000000..9ea69ed128 --- /dev/null +++ b/pkg/apis/cloudid/cloudgroupcache.go @@ -0,0 +1,47 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + CLOUD_GROUP_CACHE_STATUS_CREATING = "creating" // 创建中 + CLOUD_GROUP_CACHE_STATUS_CREATE_FAILED = "create_failed" // 创建失败 + CLOUD_GROUP_CACHE_STATUS_DELETING = "deleting" // 删除中 + CLOUD_GROUP_CACHE_STATUS_DELETE_FAILED = "delete_failed" // 删除失败 + CLOUD_GROUP_CACHE_STATUS_SYNC_STATUS = "sync_status" // 同步状态中 + CLOUD_GROUP_CACHE_STATUS_UNKNOWN = "unknown" // 未知 +) + +type CloudgroupcacheListInput struct { + apis.StatusStandaloneResourceListInput + CloudaccountResourceListInput + + // 根据权限组过滤 + CloudgroupId string `json:"cloudgroup_id"` +} + +type CloudgroupcacheCreateInput struct { +} + +type CloudgroupcacheSyncstatusInput struct { +} + +type CloudgroupcacheDetails struct { + apis.StatusStandaloneResourceDetails + SCloudgroupcache + + CloudaccountResourceDetails +} diff --git a/pkg/apis/cloudid/cloudpolicy.go b/pkg/apis/cloudid/cloudpolicy.go new file mode 100644 index 0000000000..30e5de6484 --- /dev/null +++ b/pkg/apis/cloudid/cloudpolicy.go @@ -0,0 +1,80 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + CLOUD_POLICY_STATUS_AVAILABLE = "available" + + CLOUD_POLICY_TYPE_SYSTEM = "system" + CLOUD_POLICY_TYPE_CUSTOM = "custom" +) + +type CloudpolicyListInput struct { + apis.StatusStandaloneResourceListInput + + // 根据平台过滤 + Provider []string `json:"provider"` + + // 根据子账号过滤权限 + ClouduserId string `json:"clouduser_id"` + + // 根据权限组过滤权限 + CloudgroupId string `json:"cloudgroup_id"` +} + +type CloudpolicyDetails struct { + apis.StatusStandaloneResourceDetails + SCloudpolicy +} + +type CloudpolicyCreateInput struct { + apis.StatusStandaloneResourceCreateInput + + // 云账号Id + CloudaccountId string `json:"cloudaccount_id"` + + // 权限类型 + PolicyType string `json:"policy_type"` +} + +type CloudpolicyResourceListInput struct { + // 根据公有云权限过滤资源 + Cloudpolicy string `json:"cloudpolicy"` + + // swagger:ignore + CloudpolicyId string `json:"cloudpolicy_id" "yunion:deprecated-by":"cloudpolicy"` +} + +type CloudpolicyResourceDetails struct { + // 公有云权限名称 + Cloudpolicy string `json:"cloudpolicy"` +} + +type CloudpolicyUpdateInput struct { +} + +type CloudpolicyAssignGroupInput struct { + + // 权限组Id + CloudgroupId string `json:"cloudgroup_id"` +} + +type CloudpolicyRevokeGroupInput struct { + + // 权限组Id + CloudgroupId string `json:"cloudgroup_id"` +} diff --git a/pkg/apis/cloudid/clouduser.go b/pkg/apis/cloudid/clouduser.go new file mode 100644 index 0000000000..792e3203b9 --- /dev/null +++ b/pkg/apis/cloudid/clouduser.go @@ -0,0 +1,254 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudid + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + CLOUD_USER_STATUS_CREATING = "creating" // 创建中 + CLOUD_USER_STATUS_CREATE_FAILED = "create_failed" //创建失败 + CLOUD_USER_STATUS_AVAILABLE = "available" // 可用 + CLOUD_USER_STATUS_UNKNOWN = "unknown" // 未知 + CLOUD_USER_STATUS_DELETING = "deleting" // 删除中 + CLOUD_USER_STATUS_DELETE_FAILED = "delete_failed" // 删除失败 + CLOUD_USER_STATUS_SYNC_STATUS = "sync_status" // 同步状态中 + CLOUD_USER_STATUS_SYNC = "sync" // 同步配置中 + CLOUD_USER_STATUS_SYNC_FAILED = "sync_failed" // 同步配置失败 + CLOUD_USER_STATUS_SYNC_POLICIES = "sync_policies" // 同步权限中 + CLOUD_USER_STATUS_SYNC_POLICIES_FAILED = "sync_policies_failed" // 同步权限失败 + CLOUD_USER_STATUS_SYNC_GROUPS = "sync_groups" // 同步权限组中 + CLOUD_USER_STATUS_SYNC_GROUPS_FAILED = "sync_groups_failed" // 同步权限组失败 + CLOUD_USER_STATUS_RESET_PASSWORD = "reset_password" // 重置密码中 + CLOUD_USER_STATUS_RESET_PASSWORD_FAILED = "reset_password_failed" // 重置密码失败 +) + +type ClouduserCreateInput struct { + apis.StatusUserResourceCreateInput + apis.StatusBaseResourceCreateInput + + // 云订阅ID + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 必填 | + // | Aliyun | 为空 | + // | Huawei | 为空 | + // | Azure | 为空 | + // | 腾讯云 | 为空 | + CloudproviderId string `json:"cloudprovider_id"` + // 云账号ID + // Azure云账号需要有User administrator权限,否则删操作会出现Insufficient privileges to complete the operation错误信息 + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 为空 | + // | Aliyun | 必填 | + // | Huawei | 必填 | + // | Azure | 必填 | + // | 腾讯云 | 必填 | + CloudaccountId string `json:"cloudaccount_id"` + + // 用户密码, 若is_console_login = true时, 此参数不传时会生成12位随机密码 + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 不支持此参数 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 支持 | + // | 腾讯云 | 支持 | + Password string `json:"password"` + // 是否可控制台登录 + // default: false + IsConsoleLogin *bool `json:"is_console_login"` + + // 邮箱地址 + // example: test@example.com + Email string `json:"email"` + // 手机号码 + // example: 86-1868888**** + MobilePhone string `json:"mobile_phone"` + + // 初始的权限Id列表, 权限必须属于指定的云账号 + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 至少需要一个初始权限 | + // | Aliyun | 支持 | + // | Huawei | 不支持 | + // | Azure | 不支持 | + // | 腾讯云 | 支持 | + CloudpolicyIds []string `json:"cloudpolicy_ids"` + + // 初始化权限组Id列表, 权限组必须和云账号平台属性相同 + CloudgroupIds []string `json:"cloudgroup_ids"` + + // swagger:ignore + ExternalId string `json:"external_id"` +} + +type ClouduserListInput struct { + apis.StatusUserResourceListInput + + CloudaccountResourceListInput + CloudproviderResourceListInput + + // 过滤绑定权限的子账号 + CloudpolicyId string `json:"cloudpolicy_id"` + + // 过滤属于指定权限组的子账号 + CloudgroupId string `json:"cloudgroup_id"` +} + +type ClouduserDetails struct { + apis.StatusUserResourceDetails + SClouduser + + CloudaccountResourceDetails + CloudproviderResourceDetails + + // 权限数量 + CloudpolicyCount int `json:"cloudpolicy_count"` + + // 权限组数量 + CloudgroupCount int `json:"cloudgroup_count"` + + Cloudgroups []SCloudIdBaseResource `json:"cloudgroups"` + Cloudpolicies []SCloudIdBaseResource `json:"cloudpolicies"` +} + +type ClouduserJointResourceDetails struct { + apis.JointResourceBaseDetails + + ClouduserResourceDetails +} + +type ClouduserJointsListInput struct { + apis.JointResourceBaseListInput + + ClouduserResourceListInput +} + +type ClouduserJointBaseUpdateInput struct { + apis.JointResourceBaseUpdateInput +} + +type ClouduserPolicyDetails struct { + ClouduserJointResourceDetails + + CloudpolicyResourceDetails +} + +type ClouduserPolicyListInput struct { + ClouduserJointsListInput + + CloudpolicyResourceListInput +} + +type ClouduserResourceListInput struct { + // 根据公有云用户过滤资源 + Clouduser string `json:"clouduser"` + + // swagger:ignore + ClouduserId string `json:"clouduser_id" "yunion:deprecated-by":"clouduser"` +} + +type ClouduserResourceDetails struct { + // 公有云用户名称 + Clouduser string `json:"clouduser"` + + // 云账号名称 + Cloudaccount string `json:"cloudaccount"` + + // 云订阅名称 + Cloudprovider string `json:"cloudprovider"` +} + +type ClouduserAttachPolicyInput struct { + + // 权限Id + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 支持 | + // | Aliyun | 支持 | + // | Huawei | 不支持 | + // | Azure | 不支持 | + // | 腾讯云 | 支持 | + CloudpolicyId string `json:"cloudpolicy_id"` +} + +type ClouduserSetPoliciesInput struct { + // 权限Ids + CloudpolicyIds []string `json:"cloudpolicy_ids"` +} + +type ClouduserSetGroupsInput struct { + // 权限组Ids + CloudgroupIds []string `json:"cloudgroup_ids"` +} + +type ClouduserJoinGroupInput struct { + + // 权限组Id + CloudgroupId string `json:"cloudgroup_id"` +} + +type ClouduserLeaveGroupInput struct { + // 权限组Id + CloudgroupId string `json:"cloudgroup_id"` +} + +type ClouduserDetachPolicyInput struct { + + // 权限Id + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 支持,但最少需要保留一个权限 | + // | Aliyun | 支持 | + // | Huawei | 不支持 | + // | Azure | 不支持 | + // | 腾讯云 | 支持 | + CloudpolicyId string `json:"cloudpolicy_id"` +} + +type ClouduserSyncstatusInput struct { +} + +type ClouduserSyncInput struct { +} + +type ClouduserUpdateInput struct { +} + +type ClouduserResetPasswordInput struct { + // 若此参数为空, 默认会生成随机12位密码 + // + // | 云平台 | 说明 | + // |----------|---------------------------------------------| + // | Google | 不支持 | + // | Aliyun | 支持 | + // | Huawei | 支持 | + // | Azure | 支持 | + // | 腾讯云 | 支持 | + Password string `json:"password"` +} + +type ClouduserChangeOwnerInput struct { + + // 本地用户Id + UserId string `json:"user_id"` +} diff --git a/pkg/apis/cloudid/doc.go b/pkg/apis/cloudid/doc.go new file mode 100644 index 0000000000..7d11f48f98 --- /dev/null +++ b/pkg/apis/cloudid/doc.go @@ -0,0 +1 @@ +package cloudid // import "yunion.io/x/onecloud/pkg/apis/cloudid" diff --git a/pkg/apis/cloudid/zz_generated.model.go b/pkg/apis/cloudid/zz_generated.model.go new file mode 100644 index 0000000000..0d176e539b --- /dev/null +++ b/pkg/apis/cloudid/zz_generated.model.go @@ -0,0 +1,134 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by model-api-gen. DO NOT EDIT. + +package cloudid + +import ( + "yunion.io/x/onecloud/pkg/apis" +) + +// SCloudaccount is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudaccount. +type SCloudaccount struct { + apis.SStandaloneResourceBase + apis.SDomainizedResourceBase + Provider string `json:"provider"` + Brand string `json:"brand"` + IamLoginUrl string `json:"iam_login_url"` + IsSupportCloudId *bool `json:"is_support_cloud_id,omitempty"` +} + +// SCloudaccountResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudaccountResourceBase. +type SCloudaccountResourceBase struct { + // 云账号Id + CloudaccountId string `json:"cloudaccount_id"` +} + +// SCloudgroup is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroup. +type SCloudgroup struct { + apis.SStatusInfrasResourceBase + Provider string `json:"provider"` +} + +// SCloudgroupJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroupJointsBase. +type SCloudgroupJointsBase struct { + apis.SJointResourceBase + // 用户组Id + CloudgroupId string `json:"cloudgroup_id"` +} + +// SCloudgroupPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroupPolicy. +type SCloudgroupPolicy struct { + SCloudgroupJointsBase + SCloudpolicyResourceBase +} + +// SCloudgroupUser is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroupUser. +type SCloudgroupUser struct { + SCloudgroupJointsBase + SClouduserResourceBase +} + +// SCloudgroupcache is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudgroupcache. +type SCloudgroupcache struct { + apis.SStatusStandaloneResourceBase + apis.SExternalizedResourceBase + SCloudaccountResourceBase + // 用户组Id + CloudgroupId string `json:"cloudgroup_id"` +} + +// SCloudpolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudpolicy. +type SCloudpolicy struct { + apis.SStatusStandaloneResourceBase + apis.SExternalizedResourceBase + // 权限类型 + // | 权限类型 | 说明 | + // |---------------|----------------------| + // | system | 平台内置权限 | + // | custom | 目前不支持 | + PolicyType string `json:"policy_type"` + // 平台 + Provider string `json:"provider"` +} + +// SCloudpolicyResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudpolicyResourceBase. +type SCloudpolicyResourceBase struct { + // 权限Id + CloudpolicyId string `json:"cloudpolicy_id"` +} + +// SCloudprovider is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudprovider. +type SCloudprovider struct { + apis.SStandaloneResourceBase + Provider string `json:"provider"` + CloudaccountId string `json:"cloudaccount_id"` +} + +// SCloudproviderResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SCloudproviderResourceBase. +type SCloudproviderResourceBase struct { + // 子订阅Id + CloudproviderId string `json:"cloudprovider_id"` +} + +// SClouduser is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SClouduser. +type SClouduser struct { + apis.SStatusUserResourceBase + apis.SExternalizedResourceBase + SCloudproviderResourceBase + SCloudaccountResourceBase + Secret string `json:"secret"` + // 是否可以控制台登录 + IsConsoleLogin *bool `json:"is_console_login,omitempty"` + // 手机号码 + MobilePhone string `json:"mobile_phone"` + // 邮箱地址 + Email string `json:"email"` +} + +// SClouduserJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SClouduserJointsBase. +type SClouduserJointsBase struct { + apis.SJointResourceBase + SClouduserResourceBase +} + +// SClouduserPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SClouduserPolicy. +type SClouduserPolicy struct { + SClouduserJointsBase + SCloudpolicyResourceBase +} + +// SClouduserResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudid/models.SClouduserResourceBase. +type SClouduserResourceBase struct { + ClouduserId string `json:"clouduser_id"` +} diff --git a/pkg/apis/compute/consts.go b/pkg/apis/compute/consts.go index df9535bf97..908c999501 100644 --- a/pkg/apis/compute/consts.go +++ b/pkg/apis/compute/consts.go @@ -14,7 +14,9 @@ package compute +import "yunion.io/x/onecloud/pkg/apis" + const ( - SERVICE_TYPE = "compute" + SERVICE_TYPE = apis.SERVICE_TYPE_REGION SERVICE_VERSION = "v2" ) diff --git a/pkg/apis/compute/input.go b/pkg/apis/compute/input.go index 86226b1fee..e471e637d4 100644 --- a/pkg/apis/compute/input.go +++ b/pkg/apis/compute/input.go @@ -37,13 +37,8 @@ type DeletePreventableCreateInput struct { } type KeypairListInput struct { - apis.StandaloneResourceListInput - apis.UserResourceListInput - // list in admin mode - Admin *bool `json:"admin"` - // 加密类型 // example: RSA Scheme []string `json:"scheme"` diff --git a/pkg/apis/compute/keypair.go b/pkg/apis/compute/keypair.go index 0c51b7926d..9bfff95407 100644 --- a/pkg/apis/compute/keypair.go +++ b/pkg/apis/compute/keypair.go @@ -21,7 +21,7 @@ var KEYPAIR_SCHEMAS = []string{ } type KeypairCreateInput struct { - apis.StandaloneResourceCreateInput + apis.UserResourceCreateInput // 公钥内容,若为空则自动生成公钥 PublicKey string `json:"public_key"` @@ -32,9 +32,6 @@ type KeypairCreateInput struct { // swagger:ignore Fingerprint string - // swagger:ignore - OwnerId string - // 秘钥类型 // enum: RSA // default: RSA @@ -42,14 +39,11 @@ type KeypairCreateInput struct { } type KeypairDetails struct { - apis.StandaloneResourceDetails + apis.UserResourceDetails SKeypair // 私钥长度 PrivateKeyLen int `json:"private_key_len"` // 关联云主机次数 LinkedGuestCount int `json:"linked_guest_count"` - - // 用户名称 - OwnerName string `json:"owner_name"` } diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index 0eac8e7774..b42ad96861 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -200,6 +200,10 @@ type SCloudaccount struct { ShareMode string `json:"share_mode"` // 默认值proxyapi.ProxySettingId_DIRECT ProxySettingId string `json:"proxy_setting_id"` + // 公有云子账号登录地址 + IamLoginUrl string `json:"iam_login_url"` + // 是否支持创建公有云子账号 + IsSupportCloudId *bool `json:"is_support_cloud_id,omitempty"` } // SCloudprovider is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCloudprovider. @@ -865,20 +869,6 @@ type SGuestTemplateResourceBase struct { GuestTemplateId string `json:"guest_template_id"` } -// SGuestcdrom is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestcdrom. -type SGuestcdrom struct { - Id string `json:"id"` - // = Column(VARCHAR(36, charset='ascii'), primary_key=True) - ImageId string `json:"image_id"` - // Column(VARCHAR(36, charset='ascii'), nullable=True) - Name string `json:"name"` - // Column(VARCHAR(64, charset='ascii'), nullable=True) - Path string `json:"path"` - // Column(VARCHAR(256, charset='ascii'), nullable=True) - Size int `json:"size"` - UpdateVersion int `json:"update_version"` -} - // SGuestdisk is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestdisk. type SGuestdisk struct { SGuestJointsBase @@ -998,10 +988,11 @@ type SHost struct { OvnVersion string `json:"ovn_version"` IsBaremetal bool `json:"is_baremetal"` // 是否处于维护状态 - IsMaintenance bool `json:"is_maintenance"` - EnableHealthCheck bool `json:"enable_health_check"` - ResourceType string `json:"resource_type"` - RealExternalId string `json:"real_external_id"` + IsMaintenance bool `json:"is_maintenance"` + LastPingAt time.Time `json:"last_ping_at"` + EnableHealthCheck bool `json:"enable_health_check"` + ResourceType string `json:"resource_type"` + RealExternalId string `json:"real_external_id"` // 是否为导入的宿主机 IsImport bool `json:"is_import"` // 是否允许PXE启动 @@ -1157,7 +1148,7 @@ type SIsolatedDevice struct { // SKeypair is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SKeypair. type SKeypair struct { - apis.SStandaloneResourceBase + apis.SUserResourceBase // 加密类型 // example: RSA Scheme string `json:"scheme"` @@ -1168,8 +1159,6 @@ type SKeypair struct { PrivateKey string `json:"private_key"` // 公钥 PublicKey string `json:"public_key"` - // 用户Id - OwnerId string `json:"owner_id"` } // SLoadbalancer is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SLoadbalancer. @@ -1258,9 +1247,12 @@ type SLoadbalancerAgentDeployment struct { // SLoadbalancerAgentParams is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SLoadbalancerAgentParams. type SLoadbalancerAgentParams struct { - KeepalivedConfTmpl string `json:"keepalived_conf_tmpl"` - HaproxyConfTmpl string `json:"haproxy_conf_tmpl"` - TelegrafConfTmpl string `json:"telegraf_conf_tmpl"` + KeepalivedConfTmpl string `json:"keepalived_conf_tmpl"` + HaproxyConfTmpl string `json:"haproxy_conf_tmpl"` + TelegrafConfTmpl string `json:"telegraf_conf_tmpl"` + Vrrp SLoadbalancerAgentParamsVrrp `json:"vrrp"` + Haproxy SLoadbalancerAgentParamsHaproxy `json:"haproxy"` + Telegraf SLoadbalancerAgentParamsTelegraf `json:"telegraf"` } // SLoadbalancerAgentParamsHaproxy is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SLoadbalancerAgentParamsHaproxy. @@ -1502,16 +1494,6 @@ type SManagedResourceBase struct { ManagerId string `json:"manager_id"` } -// SNatDEntry is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNatDEntry. -type SNatDEntry struct { - SNatEntry - ExternalIP string `json:"external_ip"` - ExternalPort int `json:"external_port"` - InternalIP string `json:"internal_ip"` - InternalPort int `json:"internal_port"` - IpProtocol string `json:"ip_protocol"` -} - // SNatEntry is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNatEntry. type SNatEntry struct { apis.SStatusInfrasResourceBase @@ -1542,25 +1524,6 @@ type SNatgatewayResourceBase struct { NatgatewayId string `json:"natgateway_id"` } -// SNetInterface is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNetInterface. -type SNetInterface struct { - Mac string `json:"mac"` - // Column(VARCHAR(36, charset='ascii'), primary_key=True) - BaremetalId string `json:"baremetal_id"` - // Column(VARCHAR(36, charset='ascii'), nullable=True) - WireId string `json:"wire_id"` - // Column(VARCHAR(36, charset='ascii'), nullable=True) - Rate int `json:"rate"` - // Column(Integer, nullable=True) # Mbps - NicType string `json:"nic_type"` - // Column(VARCHAR(36, charset='ascii'), nullable=True) - Index byte `json:"index"` - // Column(TINYINT, nullable=True) - LinkUp bool `json:"link_up"` - // Column(Boolean, nullable=True) - Mtu int16 `json:"mtu"` -} - // SNetwork is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNetwork. type SNetwork struct { apis.SSharableVirtualResourceBase @@ -1735,6 +1698,8 @@ type SScalingAlarm struct { Value float64 `json:"value"` // Real-time cumulate number RealCumulate int `json:"real_cumulate"` + // Last trigger time + LastTriggerTime time.Time `json:"last_trigger_time"` } // SScalingGroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SScalingGroup. @@ -1765,6 +1730,10 @@ type SScalingGroup struct { HealthCheckGov int `json:"health_check_gov"` LoadbalancerBackendPort int `json:"loadbalancer_backend_port"` LoadbalancerBackendWeight int `json:"loadbalancer_backend_weight"` + // Time to allow scale + AllowScaleTime time.Time `json:"allow_scale_time"` + // NextCheckTime descripe the next time to check instance's health + NextCheckTime time.Time `json:"next_check_time"` } // SScalingGroupGuest is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SScalingGroupGuest. @@ -1852,6 +1821,7 @@ type SScheduledTask struct { apis.SEnabledResourceBase ScheduledType string `json:"scheduled_type"` STimer + TimerDesc string `json:"timer_desc"` ResourceType string `json:"resource_type"` Operation string `json:"operation"` LabelType string `json:"label_type"` diff --git a/pkg/apis/const.go b/pkg/apis/const.go new file mode 100644 index 0000000000..0de1d188c2 --- /dev/null +++ b/pkg/apis/const.go @@ -0,0 +1,40 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apis + +const ( + SERVICE_TYPE_IMAGE = "image" + SERVICE_TYPE_OFFLINE_CLOUDMETA = "offlinecloudmeta" + SERVICE_TYPE_CLOUDID = "cloudid" + SERVICE_TYPE_CLOUDEVENT = "cloudevent" + SERVICE_TYPE_DEVTOOL = "devtool" + SERVICE_TYPE_ANSIBLE = "ansible" + SERVICE_TYPE_CLOUDMETA = "cloudmeta" + SERVICE_TYPE_WEBSOCKET = "websocket" + SERVICE_TYPE_AUTOUPDATE = "autoupdate" + SERVICE_TYPE_YUNIONCONF = "yunionconf" + SERVICE_TYPE_YUNIONAGENT = "yunionagent" + SERVICE_TYPE_METER = "meter" + SERVICE_TYPE_SCHEDULER = "scheduler" + SERVICE_TYPE_ITSM = "itsm" + SERVICE_TYPE_VNCPROXY = "vncproxy" + SERVICE_TYPE_KEYSTONE = "identity" + SERVICE_TYPE_NOTIFY = "notify" + SERVICE_TYPE_CLOUDWATCHER = "cloudwatcher" + SERVICE_TYPE_MONITOR = "monitor" + SERVICE_TYPE_SERVICETREE = "servicetree" + SERVICE_TYPE_LOG = "log" + SERVICE_TYPE_REGION = "compute" +) diff --git a/pkg/apis/identity/consts.go b/pkg/apis/identity/consts.go index cf33a2448a..3a2824fb93 100644 --- a/pkg/apis/identity/consts.go +++ b/pkg/apis/identity/consts.go @@ -14,8 +14,10 @@ package identity +import "yunion.io/x/onecloud/pkg/apis" + const ( - SERVICE_TYPE = "identity" + SERVICE_TYPE = apis.SERVICE_TYPE_KEYSTONE DEFAULT_DOMAIN_ID = "default" DEFAULT_DOMAIN_NAME = "Default" diff --git a/pkg/apis/identity/user.go b/pkg/apis/identity/user.go index ad2801482b..dbaa817281 100644 --- a/pkg/apis/identity/user.go +++ b/pkg/apis/identity/user.go @@ -30,4 +30,6 @@ type UserDetails struct { FailedAuthCount int `json:"failed_auth_count"` FailedAuthAt time.Time `json:"failed_auth_at"` PasswordExpiresAt time.Time `json:"password_expires_at"` + + ExternalResourceInfo } diff --git a/pkg/apis/identity/zz_generated.model.go b/pkg/apis/identity/zz_generated.model.go index d3fb486c96..0a6a763ccf 100644 --- a/pkg/apis/identity/zz_generated.model.go +++ b/pkg/apis/identity/zz_generated.model.go @@ -92,20 +92,6 @@ type SFederatedUser struct { DisplayName string `json:"display_name"` } -// SFederationProtocol is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SFederationProtocol. -type SFederationProtocol struct { - Id string `json:"id"` - IdpId string `json:"idp_id"` - MappingId string `json:"mapping_id"` -} - -// SFernetKey is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SFernetKey. -type SFernetKey struct { - Type string `json:"type"` - Index int `json:"index"` - Key string `json:"key"` -} - // SGroup is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SGroup. type SGroup struct { SIdentityBaseResource @@ -143,45 +129,28 @@ type SIdmapping struct { EntityType string `json:"entity_type"` } -// SIdpRemoteIds is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SIdpRemoteIds. -type SIdpRemoteIds struct { - IdpId string `json:"idp_id"` - RemoteId string `json:"remote_id"` -} - -// SImpliedRole is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SImpliedRole. -type SImpliedRole struct { - PriorRoleId string `json:"prior_role_id"` - ImpliedRoleId string `json:"implied_role_id"` -} - // SLocalUser is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SLocalUser. type SLocalUser struct { apis.SResourceBase - Id int `json:"id"` - UserId string `json:"user_id"` - DomainId string `json:"domain_id"` - Name string `json:"name"` - FailedAuthCount int `json:"failed_auth_count"` -} - -// SNonlocalUser is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SNonlocalUser. -type SNonlocalUser struct { - DomainId string `json:"domain_id"` - Name string `json:"name"` - UserId string `json:"user_id"` + Id int `json:"id"` + UserId string `json:"user_id"` + DomainId string `json:"domain_id"` + Name string `json:"name"` + FailedAuthCount int `json:"failed_auth_count"` + FailedAuthAt time.Time `json:"failed_auth_at"` } // SPassword is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SPassword. type SPassword struct { apis.SResourceBase - Id int `json:"id"` - LocalUserId int `json:"local_user_id"` - Password string `json:"password"` - SelfService bool `json:"self_service"` - PasswordHash string `json:"password_hash"` - CreatedAtInt int64 `json:"created_at_int"` - ExpiresAtInt int64 `json:"expires_at_int"` + Id int `json:"id"` + LocalUserId int `json:"local_user_id"` + Password string `json:"password"` + ExpiresAt time.Time `json:"expires_at"` + SelfService bool `json:"self_service"` + PasswordHash string `json:"password_hash"` + CreatedAtInt int64 `json:"created_at_int"` + ExpiresAtInt int64 `json:"expires_at_int"` } // SPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SPolicy. @@ -199,21 +168,6 @@ type SProject struct { IsDomain *bool `json:"is_domain,omitempty"` } -// SProjectExtended is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SProjectExtended. -type SProjectExtended struct { - SProject - DomainName string `json:"domain_name"` -} - -// SProjectResource is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SProjectResource. -type SProjectResource struct { - ProjectId string `json:"project_id"` - RegionId string `json:"region_id"` - ServiceId string `json:"service_id"` - Resource string `json:"resource"` - Count int `json:"count"` -} - // SRegion is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SRegion. type SRegion struct { apis.SStandaloneResourceBase @@ -259,13 +213,6 @@ type SUser struct { EnableMfa *bool `json:"enable_mfa,omitempty"` } -// SUserOption is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SUserOption. -type SUserOption struct { - UserId string `json:"user_id"` - OptionId string `json:"option_id"` - OptionValue string `json:"option_value"` -} - // SUsergroupMembership is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SUsergroupMembership. type SUsergroupMembership struct { apis.SResourceBase diff --git a/pkg/apis/image/consts.go b/pkg/apis/image/consts.go index 5bfa3e1b07..f7a59700cc 100644 --- a/pkg/apis/image/consts.go +++ b/pkg/apis/image/consts.go @@ -14,10 +14,12 @@ package image +import "yunion.io/x/onecloud/pkg/apis" + type TImageType string const ( - SERVICE_TYPE = "image" + SERVICE_TYPE = apis.SERVICE_TYPE_IMAGE SERVICE_VERSION = "" // https://docs.openstack.org/glance/pike/user/statuses.html diff --git a/pkg/apis/image/zz_generated.model.go b/pkg/apis/image/zz_generated.model.go index 6c8b5ab717..386a5237c2 100644 --- a/pkg/apis/image/zz_generated.model.go +++ b/pkg/apis/image/zz_generated.model.go @@ -62,44 +62,9 @@ type SImage struct { OssChecksum string `json:"oss_checksum"` } -// SImageMember is an autogenerated struct via yunion.io/x/onecloud/pkg/image/models.SImageMember. -type SImageMember struct { - SImagePeripheral - Member string `json:"member"` - CanShare bool `json:"can_share"` -} - // SImagePeripheral is an autogenerated struct via yunion.io/x/onecloud/pkg/image/models.SImagePeripheral. type SImagePeripheral struct { apis.SResourceBase Id int `json:"id"` ImageId string `json:"image_id"` } - -// SImageProperty is an autogenerated struct via yunion.io/x/onecloud/pkg/image/models.SImageProperty. -type SImageProperty struct { - SImagePeripheral - Name string `json:"name"` - Value string `json:"value"` -} - -// SImageSubformat is an autogenerated struct via yunion.io/x/onecloud/pkg/image/models.SImageSubformat. -type SImageSubformat struct { - SImagePeripheral - Format string `json:"format"` - Size int64 `json:"size"` - Location string `json:"location"` - Checksum string `json:"checksum"` - FastHash string `json:"fast_hash"` - Status string `json:"status"` - TorrentSize int64 `json:"torrent_size"` - TorrentLocation string `json:"torrent_location"` - TorrentChecksum string `json:"torrent_checksum"` - TorrentStatus string `json:"torrent_status"` -} - -// SImageTag is an autogenerated struct via yunion.io/x/onecloud/pkg/image/models.SImageTag. -type SImageTag struct { - SImagePeripheral - Value string `json:"value"` -} diff --git a/pkg/apis/input.go b/pkg/apis/input.go index 647217de9e..635d18ec60 100644 --- a/pkg/apis/input.go +++ b/pkg/apis/input.go @@ -75,6 +75,18 @@ type SharableVirtualResourceCreateInput struct { SharableResourceBaseCreateInput } +type StatusUserResourceCreateInput struct { + StatusBaseResourceCreateInput + UserResourceCreateInput +} + +type UserResourceCreateInput struct { + StandaloneResourceCreateInput + + // 本地用户Id,若为空则使用当前用户Id作为此参数值 + OwnerId string `json:"owner_id"` +} + type VirtualResourceCreateInput struct { StatusStandaloneResourceCreateInput ProjectizedResourceCreateInput diff --git a/pkg/apis/list.go b/pkg/apis/list.go index 128080b6bd..092c94a43f 100644 --- a/pkg/apis/list.go +++ b/pkg/apis/list.go @@ -53,7 +53,18 @@ type ProjectizedResourceListInput struct { OrderByTenant string `json:"order_by_tenant" "yunion:deprecated-by":"order_by_project"` } +type StatusUserResourceListInput struct { + StatusResourceBaseListInput + UserResourceListInput +} + type UserResourceListInput struct { + StandaloneResourceListInput + ScopedResourceInput + + // list in admin mode + Admin *bool `json:"admin"` + // 查询指定的用户(ID或名称)拥有的资源 User string `json:"user"` // swagger:ignore diff --git a/pkg/apis/logger/consts.go b/pkg/apis/logger/consts.go index 93d975691f..d3e519e393 100644 --- a/pkg/apis/logger/consts.go +++ b/pkg/apis/logger/consts.go @@ -14,6 +14,8 @@ package logger +import "yunion.io/x/onecloud/pkg/apis" + const ( - SERVICE_TYPE = "log" + SERVICE_TYPE = apis.SERVICE_TYPE_LOG ) diff --git a/pkg/apis/monitor/zz_generated.model.go b/pkg/apis/monitor/zz_generated.model.go index 09d3f702f0..c002b8abf2 100644 --- a/pkg/apis/monitor/zz_generated.model.go +++ b/pkg/apis/monitor/zz_generated.model.go @@ -72,16 +72,6 @@ type SDataSource struct { IsDefault *bool `json:"is_default,omitempty"` } -// SMeterAlert is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SMeterAlert. -type SMeterAlert struct { - SV1Alert -} - -// SNodeAlert is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SNodeAlert. -type SNodeAlert struct { - SV1Alert -} - // SNotification is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SNotification. type SNotification struct { apis.SVirtualResourceBase @@ -111,6 +101,10 @@ type SSuggestSysAlert struct { Provider string `json:"provider"` Project string `json:"project"` Cloudaccount string `json:"cloudaccount"` + // 费用 + Amount float64 `json:"amount"` + // 币种 + Currency string `json:"currency"` } // SSuggestSysRule is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SSuggestSysRule. @@ -123,8 +117,3 @@ type SSuggestSysRule struct { Setting interface{} `json:"setting"` ExecTime time.Time `json:"exec_time"` } - -// SV1Alert is an autogenerated struct via yunion.io/x/onecloud/pkg/monitor/models.SV1Alert. -type SV1Alert struct { - SAlert -} diff --git a/pkg/apis/notify/notify.go b/pkg/apis/notify/notify.go index f634f30b07..58e3ef7838 100644 --- a/pkg/apis/notify/notify.go +++ b/pkg/apis/notify/notify.go @@ -14,7 +14,9 @@ package notify +import "yunion.io/x/onecloud/pkg/apis" + const ( - SERVICE_TYPE = "notify" + SERVICE_TYPE = apis.SERVICE_TYPE_NOTIFY SERVICE_VERSION = "" ) diff --git a/pkg/apis/output.go b/pkg/apis/output.go index 5569f0f94d..e350b65967 100644 --- a/pkg/apis/output.go +++ b/pkg/apis/output.go @@ -121,6 +121,17 @@ type MetadataResourceInfo struct { Metadata map[string]string `json:"metadata"` } +type StatusUserResourceDetails struct { + UserResourceDetails +} + +type UserResourceDetails struct { + StandaloneResourceDetails + + // 用户名称 + OwnerName string `json:"owner_name"` +} + type StandaloneResourceDetails struct { ResourceBaseDetails diff --git a/pkg/apis/yunionconf/consts.go b/pkg/apis/yunionconf/consts.go index 6b1b7b2158..e991b418b7 100644 --- a/pkg/apis/yunionconf/consts.go +++ b/pkg/apis/yunionconf/consts.go @@ -14,6 +14,8 @@ package yunionconf +import "yunion.io/x/onecloud/pkg/apis" + const ( - SERVICE_TYPE = "yunionconf" + SERVICE_TYPE = apis.SERVICE_TYPE_YUNIONCONF ) diff --git a/pkg/apis/zz_generated.model.go b/pkg/apis/zz_generated.model.go index c5a39d432d..2b9a819140 100644 --- a/pkg/apis/zz_generated.model.go +++ b/pkg/apis/zz_generated.model.go @@ -99,60 +99,9 @@ type SJointResourceBase struct { // SKeystoneCacheObject is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SKeystoneCacheObject. type SKeystoneCacheObject struct { SStandaloneResourceBase - DomainId string `json:"domain_id"` - Domain string `json:"domain"` -} - -// SMetadata is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SMetadata. -type SMetadata struct { - // 资源类型 - // example: network - ObjType string `json:"obj_type"` - // 资源ID - // example: 87321a70-1ecb-422a-8b0c-c9aa632a46a7 - ObjId string `json:"obj_id"` - // 资源组合ID - // example: network::87321a70-1ecb-422a-8b0c-c9aa632a46a7 - Id string `json:"id"` - // 标签KEY - // exmaple: 部门 - Key string `json:"key"` - // 标签值 - // example: 技术部 - Value string `json:"value"` - // 是否被删除 - Deleted bool `json:"deleted"` -} - -// SOpsLog is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SOpsLog. -type SOpsLog struct { - Id int64 `json:"id"` - // = Column(BigInteger, primary_key=True) - ObjType string `json:"obj_type"` - // = Column(VARCHAR(40, charset='ascii'), nullable=False) - ObjId string `json:"obj_id"` - // = Column(VARCHAR(ID_LENGTH, charset='ascii'), nullable=False) - ObjName string `json:"obj_name"` - // = Column(VARCHAR(128, charset='utf8'), nullable=False) - Action string `json:"action"` - // = Column(VARCHAR(32, charset='ascii'), nullable=False) - Notes string `json:"notes"` - ProjectId string `json:"tenant_id"` - // = Column(VARCHAR(ID_LENGTH, charset='ascii')) - Project string `json:"tenant"` - ProjectDomainId string `json:"project_domain_id"` - ProjectDomain string `json:"project_domain"` - UserId string `json:"user_id"` - // = Column(VARCHAR(ID_LENGTH, charset='ascii')) - User string `json:"user"` - // = Column(VARCHAR(128, charset='utf8')) - DomainId string `json:"domain_id"` - Domain string `json:"domain"` - Roles string `json:"roles"` - // BillingType string `width:"64" charset:"ascii" default:"postpaid" list:"user" create:"user"` // billing_type = Column(VARCHAR(64, charset='ascii'), nullable=True) - OpsTime time.Time `json:"ops_time"` - OwnerDomainId string `json:"owner_domain_id"` - OwnerProjectId string `json:"owner_tenant_id"` + DomainId string `json:"domain_id"` + Domain string `json:"domain"` + LastCheck time.Time `json:"last_check"` } // SProjectizedResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SProjectizedResourceBase. @@ -247,14 +196,17 @@ type SStatusStandaloneResourceBase struct { SStatusResourceBase } -// STenant is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.STenant. -type STenant struct { - SKeystoneCacheObject +// SStatusUserResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SStatusUserResourceBase. +type SStatusUserResourceBase struct { + SUserResourceBase + SStatusResourceBase } -// SUser is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SUser. -type SUser struct { - SKeystoneCacheObject +// SUserResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SUserResourceBase. +type SUserResourceBase struct { + SStandaloneResourceBase + // 本地用户Id + OwnerId string `json:"owner_id"` } // SVirtualJointResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SVirtualJointResourceBase. diff --git a/pkg/cloudcommon/db/interface.go b/pkg/cloudcommon/db/interface.go index 94828f0131..0fbb30e592 100644 --- a/pkg/cloudcommon/db/interface.go +++ b/pkg/cloudcommon/db/interface.go @@ -323,6 +323,13 @@ type IVirtualModelManager interface { GetResourceCount() ([]SScopeResourceCount, error) } +type IUserModelManager interface { + IStandaloneModelManager + + GetIUserModelManager() IUserModelManager + GetResourceCount() ([]SScopeResourceCount, error) +} + type IVirtualModel interface { IStandaloneModel IPendingDeletable diff --git a/pkg/cloudcommon/db/project_resources.go b/pkg/cloudcommon/db/project_resources.go index 4245f08971..40d9fafcb5 100644 --- a/pkg/cloudcommon/db/project_resources.go +++ b/pkg/cloudcommon/db/project_resources.go @@ -29,13 +29,13 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/auth" ) -func AddProjectResourceCountHandler(prefix string, app *appsrv.Application) { - prefix = fmt.Sprintf("%s/project-resources", prefix) - app.AddHandler2("GET", prefix, auth.Authenticate(getAllProjectResourceCountsHandler), nil, "get_project_resources", nil) +func AddScopeResourceCountHandler(prefix string, app *appsrv.Application) { + prefix = fmt.Sprintf("%s/scope-resources", prefix) + app.AddHandler2("GET", prefix, auth.Authenticate(getAllScopeResourceCountsHandler), nil, "get_scope_resources", nil) } -func getAllProjectResourceCountsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { - cnt, err := getAllProjectResourceCounts() +func getAllScopeResourceCountsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) { + cnt, err := getAllScopeResourceCounts() if err != nil { httperrors.GeneralServerError(w, err) return @@ -43,7 +43,7 @@ func getAllProjectResourceCountsHandler(ctx context.Context, w http.ResponseWrit appsrv.SendJSON(w, jsonutils.Marshal(cnt)) } -func getAllProjectResourceCounts() (map[string][]SScopeResourceCount, error) { +func getAllScopeResourceCounts() (map[string][]SScopeResourceCount, error) { ret := make(map[string][]SScopeResourceCount) for _, manager := range globalTables { if virtman, ok := manager.(IVirtualModelManager); ok { @@ -58,6 +58,12 @@ func getAllProjectResourceCounts() (map[string][]SScopeResourceCount, error) { return nil, errors.Wrap(err, "getDomainResourceCount") } ret[domainMan.KeywordPlural()] = resCnt + } else if userMan, ok := manager.(IUserModelManager); ok { + resCnt, err := userMan.GetResourceCount() + if err != nil { + return nil, errors.Wrap(err, "getUserResourceCount") + } + ret[userMan.KeywordPlural()] = resCnt } } return ret, nil @@ -66,39 +72,30 @@ func getAllProjectResourceCounts() (map[string][]SScopeResourceCount, error) { type SScopeResourceCount struct { TenantId string `json:"tenant_id"` DomainId string `json:"domain_id"` + OwnerId string `json:"owner_id"` ResCount int `json:"res_count"` } func (virtman *SVirtualResourceBaseManager) GetResourceCount() ([]SScopeResourceCount, error) { virts := virtman.GetIVirtualModelManager().Query() - // log.Debugf("GetResourceCount: %s", virtman.keywordPlural) - return CalculateProjectResourceCount(virts) -} - -func CalculateProjectResourceCount(query *sqlchemy.SQuery) ([]SScopeResourceCount, error) { - virts := query.SubQuery() - q := virts.Query(virts.Field("tenant_id"), sqlchemy.COUNT("res_count")) - q = q.IsNotEmpty("tenant_id") - q = q.GroupBy(virts.Field("tenant_id")) - cnts := make([]SScopeResourceCount, 0) - err := q.All(&cnts) - if err != nil && err != sql.ErrNoRows { - return nil, errors.Wrap(err, "q.All") - } - return cnts, nil + return CalculateResourceCount(virts, "tenant_id") } func (domainman *SDomainLevelResourceBaseManager) GetResourceCount() ([]SScopeResourceCount, error) { virts := domainman.GetIDomainLevelModelManager().Query() - // log.Debugf("GetResourceCount: %s", virtman.keywordPlural) - return CalculateDomainResourceCount(virts) + return CalculateResourceCount(virts, "domain_id") } -func CalculateDomainResourceCount(query *sqlchemy.SQuery) ([]SScopeResourceCount, error) { +func (userman *SUserResourceBaseManager) GetResourceCount() ([]SScopeResourceCount, error) { + virts := userman.GetIUserModelManager().Query() + return CalculateResourceCount(virts, "owner_id") +} + +func CalculateResourceCount(query *sqlchemy.SQuery, field string) ([]SScopeResourceCount, error) { virts := query.SubQuery() - q := virts.Query(virts.Field("domain_id"), sqlchemy.COUNT("res_count")) - q = q.IsNotEmpty("domain_id") - q = q.GroupBy(virts.Field("domain_id")) + q := virts.Query(virts.Field(field), sqlchemy.COUNT("res_count")) + q = q.IsNotEmpty(field) + q = q.GroupBy(virts.Field(field)) cnts := make([]SScopeResourceCount, 0) err := q.All(&cnts) if err != nil && err != sql.ErrNoRows { diff --git a/pkg/cloudcommon/db/statususerresources.go b/pkg/cloudcommon/db/statususerresources.go new file mode 100644 index 0000000000..fac2be9eec --- /dev/null +++ b/pkg/cloudcommon/db/statususerresources.go @@ -0,0 +1,121 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package db + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SStatusUserResourceBase struct { + SUserResourceBase + SStatusResourceBase +} + +type SStatusUserResourceBaseManager struct { + SUserResourceBaseManager + SStatusResourceBaseManager +} + +func NewStatusUserResourceBaseManager(dt interface{}, tableName string, keyword string, keywordPlural string) SStatusUserResourceBaseManager { + return SStatusUserResourceBaseManager{ + SUserResourceBaseManager: NewUserResourceBaseManager(dt, tableName, keyword, keywordPlural), + } +} + +func (model *SStatusUserResourceBase) SetStatus(userCred mcclient.TokenCredential, status string, reason string) error { + return statusBaseSetStatus(model, userCred, status, reason) +} + +func (manager *SStatusUserResourceBaseManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input apis.StatusUserResourceCreateInput) (apis.StatusUserResourceCreateInput, error) { + var err error + input.UserResourceCreateInput, err = manager.SUserResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.UserResourceCreateInput) + if err != nil { + return input, err + } + return input, nil +} + +func (manager *SStatusUserResourceBaseManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query apis.StatusUserResourceListInput, +) (*sqlchemy.SQuery, error) { + q, err := manager.SUserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.UserResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SUserResourceBaseManager.ListItemFilter") + } + q, err = manager.SStatusResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (manager *SStatusUserResourceBaseManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query apis.StatusUserResourceListInput) (*sqlchemy.SQuery, error) { + q, err := manager.SUserResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.UserResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SUserResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SStatusResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (manager *SStatusUserResourceBaseManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := manager.SUserResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SStatusUserResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []apis.StatusUserResourceDetails { + rows := make([]apis.StatusUserResourceDetails, len(objs)) + userRows := manager.SUserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = apis.StatusUserResourceDetails{ + UserResourceDetails: userRows[i], + } + } + return rows +} + +func (model *SStatusUserResourceBase) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (apis.StatusUserResourceDetails, error) { + return apis.StatusUserResourceDetails{}, nil +} diff --git a/pkg/cloudcommon/db/userresources.go b/pkg/cloudcommon/db/userresources.go new file mode 100644 index 0000000000..c05198141d --- /dev/null +++ b/pkg/cloudcommon/db/userresources.go @@ -0,0 +1,202 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package db + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SUserResourceBaseManager struct { + SStandaloneResourceBaseManager +} + +func NewUserResourceBaseManager(dt interface{}, tableName string, keyword string, keywordPlural string) SUserResourceBaseManager { + return SUserResourceBaseManager{ + SStandaloneResourceBaseManager: NewStandaloneResourceBaseManager(dt, + tableName, keyword, keywordPlural), + } +} + +type SUserResourceBase struct { + SStandaloneResourceBase + + // 本地用户Id + OwnerId string `width:"128" charset:"ascii" index:"true" list:"user" nullable:"false" create:"required"` +} + +func (manager *SUserResourceBaseManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query apis.UserResourceListInput, +) (*sqlchemy.SQuery, error) { + q, err := manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput) + if err != nil { + return nil, err + } + if ((query.Admin != nil && *query.Admin) || query.Scope == string(rbacutils.ScopeSystem)) && IsAdminAllowList(userCred, manager) { + user := query.User + if len(user) > 0 { + uc, _ := UserCacheManager.FetchUserByIdOrName(ctx, user) + if uc == nil { + return nil, httperrors.NewUserNotFoundError("user %s not found", user) + } + q = q.Equals("owner_id", uc.Id) + } + } else { + q = q.Equals("owner_id", userCred.GetUserId()) + } + + return q, nil +} + +func (manager *SUserResourceBaseManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query apis.UserResourceListInput) (*sqlchemy.SQuery, error) { + q, err := manager.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput) + if err != nil { + return nil, err + } + return q, nil +} + +func (manager *SUserResourceBaseManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := manager.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SUserResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []apis.UserResourceDetails { + rows := make([]apis.UserResourceDetails, len(objs)) + stdRows := manager.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + userIds := make([]string, len(objs)) + for i := range rows { + rows[i] = apis.UserResourceDetails{ + StandaloneResourceDetails: stdRows[i], + } + var base *SUserResourceBase + reflectutils.FindAnonymouStructPointer(objs[i], &base) + if base != nil && len(base.OwnerId) > 0 { + userIds[i] = base.OwnerId + } + } + + userMaps, err := FetchIdNameMap2(UserCacheManager, userIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail: %v", err) + return rows + } + + for i := range rows { + rows[i].OwnerName, _ = userMaps[userIds[i]] + } + + return rows +} + +func (manager *SUserResourceBaseManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + if owner != nil { + if scope == rbacutils.ScopeUser { + if len(owner.GetUserId()) > 0 { + q = q.Equals("owner_id", owner.GetUserId()) + } + } + } + return q +} + +func (self *SUserResourceBase) GetOwnerId() mcclient.IIdentityProvider { + owner := SOwnerId{UserId: self.OwnerId} + return &owner +} + +func (model *SUserResourceBase) IsOwner(userCred mcclient.TokenCredential) bool { + return userCred.GetProjectId() == model.OwnerId +} + +func (manager *SUserResourceBaseManager) GetIUserModelManager() IUserModelManager { + return manager.GetVirtualObject().(IUserModelManager) +} + +func (manager *SUserResourceBaseManager) FetchByName(userCred mcclient.IIdentityProvider, idStr string) (IModel, error) { + return FetchByName(manager, userCred, idStr) +} + +func (manager *SUserResourceBaseManager) FetchByIdOrName(userCred mcclient.IIdentityProvider, idStr string) (IModel, error) { + return FetchByIdOrName(manager, userCred, idStr) +} + +func (manager *SUserResourceBaseManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input apis.UserResourceCreateInput) (apis.UserResourceCreateInput, error) { + if len(input.OwnerId) == 0 { + input.OwnerId = userCred.GetUserId() + } + var err error + input.StandaloneResourceCreateInput, err = manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StandaloneResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SStandaloneResourceBaseManager.ValidateCreateData") + } + return input, nil +} + +func (manager *SUserResourceBaseManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (manager *SUserResourceBaseManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return true +} + +func (self *SUserResourceBase) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return self.IsOwner(userCred) || IsAdminAllowUpdate(userCred, self) +} + +func (self *SUserResourceBase) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || IsAdminAllowDelete(userCred, self) +} + +func (self *SUserResourceBase) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || IsAdminAllowGet(userCred, self) +} + +func (manager *SUserResourceBaseManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { + return FetchUserInfo(ctx, data) +} + +func (manager *SUserResourceBaseManager) NamespaceScope() rbacutils.TRbacScope { + return rbacutils.ScopeUser +} + +func (manager *SUserResourceBaseManager) ResourceScope() rbacutils.TRbacScope { + return rbacutils.ScopeUser +} diff --git a/pkg/cloudid/models/cloudaccount.go b/pkg/cloudid/models/cloudaccount.go new file mode 100644 index 0000000000..5cff62950b --- /dev/null +++ b/pkg/cloudid/models/cloudaccount.go @@ -0,0 +1,837 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + "net/http" + "net/url" + + "golang.org/x/net/http/httpproxy" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + + proxyapi "yunion.io/x/onecloud/pkg/apis/cloudcommon/proxy" + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/options" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +// +onecloud:swagger-gen-ignore +type SCloudaccountManager struct { + db.SDomainLevelResourceBaseManager +} + +var CloudaccountManager *SCloudaccountManager + +func init() { + CloudaccountManager = &SCloudaccountManager{ + SDomainLevelResourceBaseManager: db.NewDomainLevelResourceBaseManager( + SCloudaccount{}, + "cloudaccounts_tbl", + "cloudaccount", + "cloudaccounts", + ), + } + CloudaccountManager.SetVirtualObject(CloudaccountManager) +} + +type SCloudaccount struct { + db.SStandaloneResourceBase + db.SDomainizedResourceBase + + Provider string `width:"64" charset:"ascii" list:"domain"` + Brand string `width:"64" charset:"utf8" nullable:"true" list:"domain"` + IamLoginUrl string `width:"512" charset:"ascii"` + IsSupportCloudId tristate.TriState `nullable:"false" get:"domain" list:"domain" default:"false"` +} + +func (manager *SCloudaccountManager) GetICloudaccounts() ([]SCloudaccount, error) { + s := auth.GetAdminSession(context.Background(), options.Options.Region, "") + params := jsonutils.NewDict() + result, err := modules.Cloudaccounts.List(s, params) + if err != nil { + return nil, errors.Wrap(err, "modules.Cloudaccounts.List") + } + accounts := []SCloudaccount{} + err = jsonutils.Update(&accounts, result.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return accounts, nil +} + +func (manager *SCloudaccountManager) GetCloudaccounts() ([]SCloudaccount, error) { + accounts := []SCloudaccount{} + q := manager.Query() + err := db.FetchModelObjects(manager, q, &accounts) + if err != nil { + return nil, err + } + return accounts, nil +} + +func (manager *SCloudaccountManager) syncCloudaccounts(ctx context.Context, userCred mcclient.TokenCredential) (localAccounts []SCloudaccount, result compare.SyncResult) { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + accounts, err := manager.GetICloudaccounts() + if err != nil { + result.Error(errors.Wrap(err, "GetRegionCloudaccounts")) + return + } + + dbAccounts, err := manager.GetCloudaccounts() + if err != nil { + result.Error(errors.Wrap(err, "GetLocalCloudaccounts")) + return + } + + removed := make([]SCloudaccount, 0) + commondb := make([]SCloudaccount, 0) + commonext := make([]SCloudaccount, 0) + added := make([]SCloudaccount, 0) + + err = compare.CompareSets(dbAccounts, accounts, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return + } + + for i := 0; i < len(removed); i++ { + err = removed[i].syncRemoveCloudaccount(ctx, userCred) + if err != nil { + result.AddError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].syncWithICloudaccount(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + localAccounts = append(localAccounts, commondb[i]) + result.Update() + } + + for i := 0; i < len(added); i++ { + account, err := manager.newFromICloudaccount(ctx, userCred, &added[i]) + if err != nil { + result.AddError(err) + continue + } + localAccounts = append(localAccounts, *account) + result.Add() + } + return +} + +func (self *SCloudaccount) removeCloudproviders(ctx context.Context, userCred mcclient.TokenCredential) error { + providers, err := self.GetCloudproviders() + if err != nil { + return errors.Wrap(err, "GetCloudproviders") + } + for i := range providers { + err = providers[i].Delete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "provider.Delete") + } + } + return nil +} + +func (self *SCloudaccount) GetCloudproviderId() string { + return "" +} + +func (self *SCloudaccount) removeCloudgroupcaches(ctx context.Context, userCred mcclient.TokenCredential) error { + caches, err := self.GetCloudgroupcaches() + if err != nil { + return errors.Wrap(err, "GetCloudgroupcaches") + } + for i := range caches { + err = caches[i].Delete(ctx, userCred) + if err != nil { + return errors.Wrap(err, "caches[i].Delete") + } + } + return nil +} + +func (self *SCloudaccount) syncRemoveCloudaccount(ctx context.Context, userCred mcclient.TokenCredential) error { + err := self.syncRemoveClouduser(ctx, userCred) + if err != nil { + return errors.Wrap(err, "syncRemoveClouduser") + } + + err = self.removeCloudproviders(ctx, userCred) + if err != nil { + return errors.Wrap(err, "removeCloudproviders") + } + + err = self.removeCloudgroupcaches(ctx, userCred) + if err != nil { + return errors.Wrap(err, "removeCloudgroupcaches") + } + + return nil +} + +func (self *SCloudaccount) syncRemoveClouduser(ctx context.Context, userCred mcclient.TokenCredential) error { + users, err := self.getCloudusers() + if err != nil { + return errors.Wrap(err, "getCloudusers") + } + for i := range users { + err = users[i].RealDelete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "RealDelete user %s(%s)", users[i].Name, users[i].Id) + } + } + return self.Delete(ctx, userCred) +} + +func (manager *SCloudaccountManager) newFromICloudaccount(ctx context.Context, userCred mcclient.TokenCredential, account *SCloudaccount) (*SCloudaccount, error) { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + account.SetModelManager(manager, account) + err := manager.TableSpec().Insert(ctx, account) + if err != nil { + return nil, errors.Wrap(err, "Insert") + } + + return account, nil +} + +func (self *SCloudaccount) syncWithICloudaccount(ctx context.Context, userCred mcclient.TokenCredential, account SCloudaccount) error { + _, err := db.UpdateWithLock(ctx, self, func() error { + self.Name = account.Name + self.DomainId = account.DomainId + self.Brand = account.Brand + self.IamLoginUrl = account.IamLoginUrl + self.IsSupportCloudId = account.IsSupportCloudId + return nil + }) + if err != nil { + return errors.Wrap(err, "db.UpdateWithLock") + } + return nil +} + +func (manager *SCloudaccountManager) SyncCloudaccounts(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + localAccounts, result := manager.syncCloudaccounts(ctx, userCred) + log.Infof("SyncCloudaccounts: %s", result.Result()) + for i, account := range localAccounts { + lockman.LockObject(ctx, &localAccounts[i]) + defer lockman.ReleaseObject(ctx, &localAccounts[i]) + + factory, err := account.GetProviderFactory() + if err != nil { + log.Errorf("GetProviderFactory: %v", err) + continue + } + if !factory.IsClouduserBelongCloudprovider() { + continue + } + result = account.syncCloudprovider(ctx, userCred) + log.Infof("sync cloudprovider for cloudaccount %s(%s) result: %s", account.Name, account.Id, result.Result()) + } +} + +func (self SCloudaccount) GetGlobalId() string { + return self.Id +} + +func (self SCloudaccount) GetExternalId() string { + return self.Id +} + +func (manager *SCloudaccountManager) FetchAccount(ctx context.Context, id string) (*SCloudaccount, error) { + account, err := manager.FetchById(id) + if err != nil { + if err == sql.ErrNoRows { + session := auth.GetAdminSession(context.Background(), options.Options.Region, "") + result, err := modules.Cloudaccounts.Get(session, id, nil) + if err != nil { + return nil, errors.Wrap(err, "Cloudaccounts.Get") + } + _account := &SCloudaccount{} + _account.SetModelManager(manager, _account) + err = result.Unmarshal(_account) + if err != nil { + return nil, errors.Wrap(err, "result.Unmarshal") + } + + lockman.LockRawObject(ctx, manager.KeywordPlural(), id) + defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), id) + return _account, manager.TableSpec().InsertOrUpdate(ctx, _account) + } + return nil, errors.Wrap(err, "manager.FetchById") + } + return account.(*SCloudaccount), nil +} + +type SCloudDelegate struct { + Id string + Name string + Enabled bool + Status string + SyncStatus string + + AccessUrl string + Account string + Secret string + + Provider string + Brand string + + ProxySetting proxyapi.SProxySetting +} + +func (self *SCloudaccount) getCloudDelegate(ctx context.Context) (*SCloudDelegate, error) { + s := auth.GetAdminSession(ctx, options.Options.Region, "") + result, err := modules.Cloudaccounts.Get(s, self.Id, nil) + if err != nil { + return nil, errors.Wrap(err, "Cloudaccounts.Get") + } + account := &SCloudDelegate{} + err = result.Unmarshal(account) + if err != nil { + return nil, errors.Wrap(err, "result.Unmarshal") + } + return account, nil +} + +func (self *SCloudaccount) GetProvider() (cloudprovider.ICloudProvider, error) { + delegate, err := self.getCloudDelegate(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "getCloudDelegate") + } + return delegate.GetProvider() +} + +func (self *SCloudaccount) GetCloudDelegaes(ctx context.Context) ([]SCloudDelegate, error) { + s := auth.GetAdminSession(ctx, options.Options.Region, "") + params := map[string]string{"cloudaccount": self.Id} + result, err := modules.Cloudproviders.List(s, jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrap(err, "Cloudproviders.List") + } + providers := []SCloudDelegate{} + err = jsonutils.Update(&providers, result.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return providers, nil +} + +func (account *SCloudDelegate) getPassword() (string, error) { + return utils.DescryptAESBase64(account.Id, account.Secret) +} + +func (account *SCloudDelegate) getAccessUrl() string { + return account.AccessUrl +} + +func (self *SCloudaccount) GetProviderFactory() (cloudprovider.ICloudProviderFactory, error) { + return cloudprovider.GetProviderFactory(self.Provider) +} + +func (account *SCloudDelegate) GetProvider() (cloudprovider.ICloudProvider, error) { + if !account.Enabled { + return nil, errors.Errorf("Cloud account %s is not enabled", account.Name) + } + + accessUrl := account.getAccessUrl() + passwd, err := account.getPassword() + if err != nil { + return nil, err + } + var proxyFunc httputils.TransportProxyFunc + { + cfg := &httpproxy.Config{ + HTTPProxy: account.ProxySetting.HTTPProxy, + HTTPSProxy: account.ProxySetting.HTTPSProxy, + NoProxy: account.ProxySetting.NoProxy, + } + cfgProxyFunc := cfg.ProxyFunc() + proxyFunc = func(req *http.Request) (*url.URL, error) { + return cfgProxyFunc(req.URL) + } + } + return cloudprovider.GetProvider(cloudprovider.ProviderConfig{ + Id: account.Id, + Name: account.Name, + Vendor: account.Provider, + URL: accessUrl, + Account: account.Account, + Secret: passwd, + ProxyFunc: proxyFunc, + }) +} + +func (manager *SCloudaccountManager) SyncCloudusers(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + accounts, err := manager.GetCloudaccounts() + if err != nil { + log.Errorf("GetLocalCloudaccounts: %v", err) + return + } + for i := range accounts { + factory, err := accounts[i].GetProviderFactory() + if err != nil { + continue + } + if factory.IsSupportClouduser() { + err = accounts[i].StartSyncCloudusersTask(ctx, userCred, "") + if err != nil { + log.Errorf("StartSyncCloudusersTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err) + } + } + } +} + +func (self *SCloudaccount) StartSyncCloudusersTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudusersTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudaccount) StartSyncCloudgroupsTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudgroupsTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudaccount) StartSyncCloudpoliciesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudpoliciesTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudaccount) getCloudusers() ([]SClouduser, error) { + users := []SClouduser{} + q := ClouduserManager.Query().Equals("cloudaccount_id", self.Id) + err := db.FetchModelObjects(ClouduserManager, q, &users) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return users, nil +} + +func (self *SCloudaccount) GetCloudusersByProviderId(cloudproviderId string) ([]SClouduser, error) { + users := []SClouduser{} + q := ClouduserManager.Query().Equals("status", api.CLOUD_USER_STATUS_AVAILABLE).Equals("cloudaccount_id", self.Id) + if len(cloudproviderId) > 0 { + q = q.Equals("cloudprovider_id", cloudproviderId) + } + err := db.FetchModelObjects(ClouduserManager, q, &users) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return users, nil +} + +func (self *SCloudaccount) SyncCloudusers(ctx context.Context, userCred mcclient.TokenCredential, cloudproviderId string, iUsers []cloudprovider.IClouduser) ([]SClouduser, []cloudprovider.IClouduser, compare.SyncResult) { + result := compare.SyncResult{} + dbUsers, err := self.GetCloudusersByProviderId(cloudproviderId) + if err != nil { + result.Error(errors.Wrap(err, "GetCloudusersByProviderId")) + return nil, nil, result + } + + localUsers := []SClouduser{} + remoteUsers := []cloudprovider.IClouduser{} + + removed := make([]SClouduser, 0) + commondb := make([]SClouduser, 0) + commonext := make([]cloudprovider.IClouduser, 0) + added := make([]cloudprovider.IClouduser, 0) + + err = compare.CompareSets(dbUsers, iUsers, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return nil, nil, result + } + + for i := 0; i < len(removed); i++ { + if len(removed[i].ExternalId) > 0 { + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.AddError(err) + continue + } + result.Delete() + } + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].SyncWithClouduser(ctx, userCred, commonext[i], cloudproviderId) + if err != nil { + result.UpdateError(err) + continue + } + localUsers = append(localUsers, commondb[i]) + remoteUsers = append(remoteUsers, commonext[i]) + result.Update() + } + + for i := 0; i < len(added); i++ { + if added[i].GetName() != cloudprovider.TEST_CLOUDID_USER_NAME { + user, err := ClouduserManager.newFromClouduser(ctx, userCred, added[i], self.Id, cloudproviderId) + if err != nil { + result.AddError(err) + continue + } + localUsers = append(localUsers, *user) + remoteUsers = append(remoteUsers, added[i]) + result.Add() + } + } + + return localUsers, remoteUsers, result +} + +func (self *SCloudaccount) GetCloudpolicies() ([]SCloudpolicy, error) { + q := CloudpolicyManager.Query().Equals("provider", self.Provider) + policies := []SCloudpolicy{} + err := db.FetchModelObjects(CloudpolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return policies, nil +} + +func (self *SCloudaccount) SyncCloudpolicies(ctx context.Context, userCred mcclient.TokenCredential, iPolicies []cloudprovider.ICloudpolicy) compare.SyncResult { + result := compare.SyncResult{} + dbPolicies, err := self.GetCloudpolicies() + if err != nil { + result.Error(errors.Wrap(err, "GetCloudpolicies")) + return result + } + + removed := make([]SCloudpolicy, 0) + commondb := make([]SCloudpolicy, 0) + commonext := make([]cloudprovider.ICloudpolicy, 0) + added := make([]cloudprovider.ICloudpolicy, 0) + + err = compare.CompareSets(dbPolicies, iPolicies, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i++ { + err = removed[i].Delete(ctx, userCred) + if err != nil { + result.AddError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].SyncWithCloudpolicy(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + + for i := 0; i < len(added); i++ { + err := CloudpolicyManager.newFromCloudpolicy(ctx, userCred, added[i], self.Provider) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + + return result +} + +func (self *SCloudaccount) GetCloudproviders() ([]SCloudprovider, error) { + q := CloudproviderManager.Query().Equals("cloudaccount_id", self.Id) + providers := []SCloudprovider{} + err := db.FetchModelObjects(CloudproviderManager, q, &providers) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return providers, nil +} + +func (self *SCloudaccount) GetICloudprovider() ([]SCloudprovider, error) { + s := auth.GetAdminSession(context.Background(), options.Options.Region, "") + params := map[string]string{"cloudaccount": self.Id} + result, err := modules.Cloudproviders.List(s, jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrap(err, "Cloudproviders.List") + } + providers := []SCloudprovider{} + err = jsonutils.Update(&providers, result.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return providers, nil +} + +func (self *SCloudaccount) syncCloudprovider(ctx context.Context, userCred mcclient.TokenCredential) compare.SyncResult { + result := compare.SyncResult{} + + providers, err := self.GetICloudprovider() + if err != nil { + result.Error(errors.Wrap(err, "GetRegionCloudprovider")) + return result + } + + dbProviders, err := self.GetCloudproviders() + if err != nil { + result.Error(errors.Wrap(err, "GetCloudproviders")) + return result + } + + removed := make([]SCloudprovider, 0) + commondb := make([]SCloudprovider, 0) + commonext := make([]SCloudprovider, 0) + added := make([]SCloudprovider, 0) + + err = compare.CompareSets(dbProviders, providers, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i++ { + err = removed[i].syncRemoveClouduser(ctx, userCred) + if err != nil { + result.AddError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].syncWithRegionProvider(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + + for i := 0; i < len(added); i++ { + err = CloudproviderManager.newFromRegionProvider(ctx, userCred, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + return result +} + +func (self *SCloudaccount) GetCloudgroups() ([]SCloudgroup, error) { + groups := []SCloudgroup{} + q := CloudgroupManager.Query().Equals("provider", self.Provider).Equals("domain_id", self.DomainId) + err := db.FetchModelObjects(CloudgroupManager, q, &groups) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return groups, nil +} + +func (self *SCloudaccount) GetCloudgroupcaches() ([]SCloudgroupcache, error) { + caches := []SCloudgroupcache{} + q := CloudgroupcacheManager.Query().Equals("cloudaccount_id", self.Id) + err := db.FetchModelObjects(CloudgroupcacheManager, q, &caches) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return caches, nil +} + +func (manager *SCloudaccountManager) GetSupportCreateCloudgroupAccounts() ([]SCloudaccount, error) { + accounts := []SCloudaccount{} + q := manager.Query().In("provider", cloudprovider.GetSupportCloudgroupProviders()) + err := db.FetchModelObjects(manager, q, &accounts) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return accounts, nil +} + +func (manager *SCloudaccountManager) SyncCloudpolicies(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + accounts, err := manager.GetCloudaccounts() + if err != nil { + log.Errorf("GetCloudaccounts error: %v", err) + return + } + for i := range accounts { + err = accounts[i].StartSyncCloudpolicyTask(ctx, userCred, "") + if err != nil { + log.Errorf("StartSyncCloudpolicyTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err) + } + } +} + +func (self *SCloudaccount) StartSyncCloudpolicyTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudpoliciesTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (manager *SCloudaccountManager) SyncCloudgroups(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + accounts, err := manager.GetSupportCreateCloudgroupAccounts() + if err != nil { + log.Errorf("GetSupportCreateCloudgroupAccounts error: %v", err) + return + } + for i := range accounts { + err = accounts[i].StartSyncCloudgroupcacheTask(ctx, userCred, "") + if err != nil { + log.Errorf("StartSyncCloudgroupcacheTask for account %s(%s) error: %v", accounts[i].Name, accounts[i].Provider, err) + } + } +} + +func (self *SCloudaccount) StartSyncCloudgroupcacheTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + params := jsonutils.NewDict() + task, err := taskman.TaskManager.NewTask(ctx, "SyncCloudgroupcachesTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudaccount) SyncCloudgroupcaches(ctx context.Context, userCred mcclient.TokenCredential, iGroups []cloudprovider.ICloudgroup) compare.SyncResult { + result := compare.SyncResult{} + + dbCaches, err := self.GetCloudgroupcaches() + if err != nil { + result.Error(errors.Wrap(err, "GetCloudgroupcaches")) + return result + } + + removed := make([]SCloudgroupcache, 0) + commondb := make([]SCloudgroupcache, 0) + commonext := make([]cloudprovider.ICloudgroup, 0) + added := make([]cloudprovider.ICloudgroup, 0) + + err = compare.CompareSets(dbCaches, iGroups, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return result + } + + for i := 0; i < len(removed); i++ { + if len(removed[i].ExternalId) > 0 { // 只删除云上已经删除过的组 + err = removed[i].RealDelete(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + } + + for i := 0; i < len(commondb); i++ { + err = commondb[i].syncWithCloudgrup(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + + for i := 0; i < len(added); i++ { + group, err := self.GetOrCreateCloudgroup(ctx, userCred, added[i]) + if err != nil { + result.AddError(err) + continue + } + err = CloudgroupcacheManager.newFromCloudgroup(ctx, userCred, added[i], group, self.Id) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + return result +} + +func (self *SCloudaccount) GetOrCreateCloudgroup(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup) (*SCloudgroup, error) { + groups, err := self.GetCloudgroups() + if err != nil { + return nil, errors.Wrap(err, "GetCloudgroups") + } + iPolicies, err := iGroup.GetISystemCloudpolicies() + if err != nil { + return nil, errors.Wrap(err, "GetICloudpolicies") + } + for i := range groups { + isEqual, err := groups[i].IsEqual(iPolicies) + if err != nil { + return nil, errors.Wrap(err, "IsEqual") + } + if isEqual { + return &groups[i], nil + } + } + group, err := CloudgroupManager.newCloudgroup(ctx, userCred, iGroup, self.Provider, self.DomainId) + if err != nil { + return nil, errors.Wrap(err, "newCloudgroup") + } + for i := range iPolicies { + err = group.attachPolicyFromCloudpolicy(ctx, userCred, iPolicies[i]) + if err != nil { + return nil, errors.Wrap(err, "attachPolicyFromCloudpolicy") + } + } + return group, nil +} diff --git a/pkg/cloudid/models/cloudaccountresource.go b/pkg/cloudid/models/cloudaccountresource.go new file mode 100644 index 0000000000..b2659ffc0e --- /dev/null +++ b/pkg/cloudid/models/cloudaccountresource.go @@ -0,0 +1,105 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudaccountResourceBaseManager struct { +} + +type SCloudaccountResourceBase struct { + // 云账号Id + CloudaccountId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" json:"cloudaccount_id"` +} + +func (manager *SCloudaccountResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudaccountResourceListInput) (*sqlchemy.SQuery, error) { + if len(query.Cloudaccount) > 0 { + account, err := CloudaccountManager.FetchByIdOrName(nil, query.Cloudaccount) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudaccount", query.Cloudaccount) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("cloudaccount_id", account.GetId()) + } + if len(query.Provider) > 0 { + sq := CloudaccountManager.Query().SubQuery() + q = q.Join(sq, sqlchemy.Equals(q.Field("cloudaccount_id"), sq.Field("id"))).Filter(sqlchemy.In(sq.Field("provider"), query.Provider)) + } + return q, nil +} + +func (manager *SCloudaccountResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudaccountResourceDetails { + rows := make([]api.CloudaccountResourceDetails, len(objs)) + accountIds := make([]string, len(objs)) + for i := range objs { + var base *SCloudaccountResourceBase + err := reflectutils.FindAnonymouStructPointer(objs[i], &base) + if err != nil { + log.Errorf("Cannot find SCloudaccountResourceBase in %#v: %s", objs[i], err) + } else if base != nil && len(base.CloudaccountId) > 0 { + accountIds[i] = base.CloudaccountId + } + } + accounts := make(map[string]SCloudaccount) + err := db.FetchStandaloneObjectsByIds(CloudaccountManager, accountIds, &accounts) + if err != nil { + log.Errorf("FetchStandaloneObjectsByIds fail %v", err) + return rows + } + for i := range rows { + if account, ok := accounts[accountIds[i]]; ok { + rows[i].Cloudaccount = account.Name + rows[i].Provider = account.Provider + rows[i].Brand = account.Brand + if len(rows[i].Brand) == 0 { + rows[i].Brand = account.Provider + } + rows[i].IamLoginUrl = account.IamLoginUrl + } + } + return rows +} + +func (self *SCloudaccountResourceBase) GetCloudaccount() (*SCloudaccount, error) { + account, err := CloudaccountManager.FetchById(self.CloudaccountId) + if err != nil { + return nil, errors.Wrap(err, "CloudaccountManager.FetchById") + } + return account.(*SCloudaccount), nil +} diff --git a/pkg/cloudid/models/cloudgroup.go b/pkg/cloudid/models/cloudgroup.go new file mode 100644 index 0000000000..a92fc2fe3d --- /dev/null +++ b/pkg/cloudid/models/cloudgroup.go @@ -0,0 +1,774 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "gopkg.in/fatih/set.v0" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudgroupManager struct { + db.SStatusInfrasResourceBaseManager +} + +var CloudgroupManager *SCloudgroupManager + +func init() { + CloudgroupManager = &SCloudgroupManager{ + SStatusInfrasResourceBaseManager: db.NewStatusInfrasResourceBaseManager( + SCloudgroup{}, + "cloudgroups_tbl", + "cloudgroup", + "cloudgroups", + ), + } + CloudgroupManager.SetVirtualObject(CloudgroupManager) +} + +type SCloudgroup struct { + db.SStatusInfrasResourceBase + + Provider string `width:"64" charset:"ascii" list:"domain" create:"required"` +} + +func (manager *SCloudgroupManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsDomainAllowList(userCred, manager) +} + +// 权限组列表 +func (manager *SCloudgroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudgroupListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusInfrasResourceBaseListInput) + if err != nil { + return nil, err + } + + if len(query.Provider) > 0 { + q = q.In("provider", query.Provider) + } + + if len(query.ClouduserId) > 0 { + _, err = ClouduserManager.FetchById(query.ClouduserId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("clouduser", query.ClouduserId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "ClouduserManager.FetchById")) + } + sq := CloudgroupUserManager.Query("cloudgroup_id").Equals("clouduser_id", query.ClouduserId) + q = q.In("id", sq.SubQuery()) + } + + if len(query.CloudpolicyId) > 0 { + _, err = CloudpolicyManager.FetchById(query.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", query.CloudpolicyId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "CloudpolicyManager.FetchById")) + } + sq := CloudgroupPolicyManager.Query("cloudgroup_id").Equals("cloudpolicy_id", query.CloudpolicyId) + q = q.In("id", sq.SubQuery()) + } + + return q, nil +} + +func (manager *SCloudgroupManager) FetchParentId(ctx context.Context, data jsonutils.JSONObject) string { + provider, _ := data.GetString("provider") + return provider +} + +func (manager *SCloudgroupManager) FilterByParentId(q *sqlchemy.SQuery, parentId string) *sqlchemy.SQuery { + if len(parentId) > 0 { + return q.Equals("provider", parentId) + } + return q +} + +// 更新权限组 +func (self *SCloudgroup) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupUpdateInput) (api.CloudgroupUpdateInput, error) { + return input, nil +} + +// 获取权限组详情 +func (self *SCloudgroup) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudgroupDetails, error) { + return api.CloudgroupDetails{}, nil +} + +func (manager *SCloudgroupManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudgroupDetails { + rows := make([]api.CloudgroupDetails, len(objs)) + statusRows := manager.SStatusInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.CloudgroupDetails{ + StatusInfrasResourceBaseDetails: statusRows[i], + Cloudpolicies: []api.SCloudIdBaseResource{}, + } + group := objs[i].(*SCloudgroup) + rows[i].CloudgroupcacheCount, _ = group.GetCloudgroupcacheCount() + policies, _ := group.GetCloudpolicies() + for _, policy := range policies { + rows[i].Cloudpolicies = append(rows[i].Cloudpolicies, api.SCloudIdBaseResource{Id: policy.Id, Name: policy.Name}) + } + rows[i].CloudgroupcacheCount = len(policies) + users, _ := group.GetCloudusers() + for _, user := range users { + rows[i].Cloudusers = append(rows[i].Cloudusers, api.SCloudIdBaseResource{Id: user.Id, Name: user.Name}) + } + rows[i].ClouduserCount = len(users) + } + return rows +} + +// 创建权限组 +func (manager *SCloudgroupManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.CloudgroupCreateInput) (api.CloudgroupCreateInput, error) { + if len(input.Provider) == 0 { + return input, httperrors.NewMissingParameterError("provider") + } + factory, err := cloudprovider.GetProviderFactory(input.Provider) + if err != nil { + return input, errors.Wrap(err, "cloudprovider.GetProviderFactory") + } + if !factory.IsSupportClouduser() { + return input, httperrors.NewUnsupportOperationError("Unsupport cloudgroup for %s", input.Provider) + } + for _, cloudpolicyId := range input.CloudpolicyIds { + _policy, err := CloudpolicyManager.FetchById(cloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2("cloudpolicy", cloudpolicyId) + } + return input, httperrors.NewGeneralError(err) + } + policy := _policy.(*SCloudpolicy) + if policy.Provider != input.Provider { + return input, httperrors.NewConflictError("cloudpolicy %s(%s) and cloudgroup not with same provider", policy.Name, policy.Id) + } + } + input.Status = api.CLOUD_GROUP_STATUS_AVAILABLE + return input, nil +} + +func (self *SCloudgroup) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + input := api.CloudgroupCreateInput{} + data.Unmarshal(&input) + for _, policyId := range input.CloudpolicyIds { + self.attachPolicy(policyId) + } +} + +func (manager *SCloudgroupManager) newCloudgroup(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup, provider, domainId string) (*SCloudgroup, error) { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + group := &SCloudgroup{} + group.Name = iGroup.GetName() + group.Description = iGroup.GetDescription() + group.Provider = provider + group.DomainId = domainId + group.Status = api.CLOUD_GROUP_STATUS_AVAILABLE + group.SetModelManager(manager, group) + err := manager.TableSpec().Insert(ctx, group) + if err != nil { + return nil, errors.Wrap(err, "Insert") + } + return group, nil +} + +// 删除权限组 +func (self *SCloudgroup) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + params := jsonutils.NewDict() + return self.StartCloudgroupDeleteTask(ctx, userCred, params, "") +} + +func (self *SCloudgroup) StartCloudgroupDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupDeleteTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_GROUP_STATUS_DELETING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudgroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SCloudgroup) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := self.removePolicies() + if err != nil { + return errors.Wrap(err, "removePolicies") + } + err = self.removeUsers() + if err != nil { + return errors.Wrap(err, "remoteUsers") + } + return self.SStatusInfrasResourceBase.Delete(ctx, userCred) +} + +func (self *SCloudgroup) removeUsers() error { + users, err := self.GetCloudusers() + if err != nil { + return errors.Wrap(err, "GetCloudusers") + } + for i := range users { + err = self.removeUser(users[i].Id) + if err != nil { + return errors.Wrapf(err, "removeUser(%s)", users[i].Id) + } + } + return nil +} + +func (self *SCloudgroup) removePolicies() error { + policies, err := self.GetCloudpolicies() + if err != nil { + return errors.Wrap(err, "GetCloudpolicies") + } + for i := range policies { + err = self.detachPolicy(policies[i].Id) + if err != nil { + return errors.Wrapf(err, "detachPolicy(%s)", policies[i].Id) + } + } + return nil +} + +func (self *SCloudgroup) GetCloudpolicyQuery() *sqlchemy.SQuery { + q := CloudpolicyManager.Query() + sq := CloudgroupPolicyManager.Query().SubQuery() + return q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("cloudpolicy_id"))).Filter(sqlchemy.Equals(sq.Field("cloudgroup_id"), self.Id)) +} + +func (self *SCloudgroup) GetCloudpolicyCount() (int, error) { + return self.GetCloudpolicyQuery().CountWithError() +} + +func (self *SCloudgroup) GetCloudgroupcacheQuery() *sqlchemy.SQuery { + return CloudgroupcacheManager.Query().Equals("cloudgroup_id", self.Id) +} + +func (self *SCloudgroup) GetCloudgroupcacheCount() (int, error) { + return self.GetCloudgroupcacheQuery().CountWithError() +} + +func (self *SCloudgroup) GetCloudgroupcaches() ([]SCloudgroupcache, error) { + caches := []SCloudgroupcache{} + q := self.GetCloudgroupcacheQuery() + err := db.FetchModelObjects(CloudgroupcacheManager, q, &caches) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return caches, nil +} + +func (self *SCloudgroup) GetCloudpolicies() ([]SCloudpolicy, error) { + policies := []SCloudpolicy{} + q := self.GetCloudpolicyQuery() + err := db.FetchModelObjects(CloudpolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return policies, nil +} + +func (self *SCloudgroup) GetCloudpolicy(policyId string) (*SCloudpolicy, error) { + policies := []SCloudpolicy{} + q := self.GetCloudpolicyQuery().Equals("id", policyId) + err := db.FetchModelObjects(CloudpolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + if len(policies) > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + if len(policies) == 0 { + return nil, sql.ErrNoRows + } + return &policies[0], nil +} + +func (self *SCloudgroup) detachPolicy(policyId string) error { + policies := []SCloudgroupPolicy{} + q := CloudgroupPolicyManager.Query().Equals("cloudgroup_id", self.Id).Equals("cloudpolicy_id", policyId) + err := db.FetchModelObjects(CloudgroupPolicyManager, q, &policies) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + for i := range policies { + err = policies[i].Delete(context.Background(), nil) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + return nil +} + +func (self *SCloudgroup) GetClouduserQuery() *sqlchemy.SQuery { + q := ClouduserManager.Query() + sq := CloudgroupUserManager.Query().SubQuery() + return q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("clouduser_id"))).Filter(sqlchemy.Equals(sq.Field("cloudgroup_id"), self.Id)) +} + +func (self *SCloudgroup) GetClouduserCount() (int, error) { + return self.GetClouduserQuery().CountWithError() +} + +func (self *SCloudgroup) GetClouduser(userId string) (*SClouduser, error) { + users := []SClouduser{} + q := self.GetClouduserQuery().Equals("id", userId) + err := db.FetchModelObjects(ClouduserManager, q, &users) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + if len(users) > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + if len(users) == 0 { + return nil, sql.ErrNoRows + } + return &users[0], nil +} + +func (self *SCloudgroup) GetCloudusers() ([]SClouduser, error) { + users := []SClouduser{} + q := self.GetClouduserQuery() + err := db.FetchModelObjects(ClouduserManager, q, &users) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return users, nil +} + +func (self *SCloudgroup) removeUser(userId string) error { + users := []SCloudgroupUser{} + q := CloudgroupUserManager.Query().Equals("cloudgroup_id", self.Id).Equals("clouduser_id", userId) + err := db.FetchModelObjects(CloudgroupUserManager, q, &users) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + for i := range users { + err = users[i].Delete(context.Background(), nil) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + return nil +} + +func (self *SCloudgroup) GetProviderFactory() (cloudprovider.ICloudProviderFactory, error) { + return cloudprovider.GetProviderFactory(self.Provider) +} + +func (self *SCloudgroup) AllowPerformAddUser(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "add-user") +} + +// 向权限组加入用户 +// 权限组状态必须为: available +func (self *SCloudgroup) PerformAddUser(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupAddUserInput) (jsonutils.JSONObject, error) { + if len(input.ClouduserId) == 0 { + return nil, httperrors.NewMissingParameterError("clouduser_id") + } + _user, err := ClouduserManager.FetchById(input.ClouduserId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("clouduser", input.ClouduserId) + } + return nil, httperrors.NewGeneralError(err) + } + user := _user.(*SClouduser) + account, err := user.GetCloudaccount() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + if account.Provider != self.Provider { + return nil, httperrors.NewDuplicateResourceError("group and user not with same provider") + } + + _, err = self.GetClouduser(input.ClouduserId) + if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry { + return nil, httperrors.NewDuplicateResourceError("user %s has aleady in this group", input.ClouduserId) + } + + err = self.addUser(input.ClouduserId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "addUser")) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_ADD_USER, user, userCred, true) + logclient.AddSimpleActionLog(user, logclient.ACT_ADD_USER, self, userCred, true) + return nil, self.StartCloudgroupSyncUsersTask(ctx, userCred, "") +} + +func (self *SCloudgroup) StartCloudgroupSyncUsersTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupSyncUsersTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_GROUP_STATUS_SYNC_USERS, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudgroup) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "syncstatus") +} + +// 同步权限组状态 +func (self *SCloudgroup) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSyncstatusInput) (jsonutils.JSONObject, error) { + self.SetStatus(userCred, api.CLOUD_USER_STATUS_AVAILABLE, "syncstatus") + return nil, nil +} + +func (self *SCloudgroup) AllowPerformRemoveUser(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "remove-user") +} + +// 从权限组移除用户 +// 权限组状态必须为: available +func (self *SCloudgroup) PerformRemoveUser(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupRemoveUserInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_GROUP_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not remove user in status %s", self.Status) + } + if len(input.ClouduserId) == 0 { + return nil, httperrors.NewMissingParameterError("clouduser_id") + } + _user, err := ClouduserManager.FetchById(input.ClouduserId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("clouduser", input.ClouduserId) + } + return nil, httperrors.NewGeneralError(err) + } + user := _user.(*SClouduser) + + _, err = self.GetClouduser(input.ClouduserId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, nil + } + return nil, httperrors.NewGeneralError(err) + } + err = self.removeUser(input.ClouduserId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "RemoveUser")) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_REMOVE_USER, user, userCred, true) + logclient.AddSimpleActionLog(user, logclient.ACT_REMOVE_USER, self, userCred, true) + return nil, self.StartCloudgroupSyncUsersTask(ctx, userCred, "") +} + +func (self *SCloudgroup) addUser(userId string) error { + gu := &SCloudgroupUser{} + gu.SetModelManager(CloudgroupUserManager, gu) + gu.ClouduserId = userId + gu.CloudgroupId = self.Id + return CloudgroupUserManager.TableSpec().Insert(context.Background(), gu) +} + +func (self *SCloudgroup) AllowPerformSetUsers(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "set-users") +} + +// 设置权限组用户(全量覆盖) +// 权限组状态必须为: available +func (self *SCloudgroup) PerformSetUsers(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSetUsersInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_GROUP_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not set users in status %s", self.Status) + } + + users, err := self.GetCloudusers() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + userMaps := map[string]*SClouduser{} + local := set.New(set.ThreadSafe) + for i := range users { + local.Add(users[i].Id) + userMaps[users[i].Id] = &users[i] + } + + newU := set.New(set.ThreadSafe) + for _, userId := range input.ClouduserIds { + _user, err := ClouduserManager.FetchById(userId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, httperrors.NewResourceNotFoundError2("clouduser", userId) + } + return nil, httperrors.NewGeneralError(err) + } + user := _user.(*SClouduser) + account, err := user.GetCloudaccount() + if err != nil { + return nil, errors.Wrap(err, "user.GetCloudaccount") + } + if account.Provider != self.Provider { + return nil, httperrors.NewConflictError("user %s(%s) and group not with same provider", user.Name, user.Id) + } + newU.Add(userId) + userMaps[user.Id] = user + } + + for _, del := range set.Difference(local, newU).List() { + id := del.(string) + user, ok := userMaps[id] + if ok { + err = self.removeUser(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "removeUser")) + } + logclient.AddSimpleActionLog(self, logclient.ACT_REMOVE_USER, user, userCred, true) + logclient.AddSimpleActionLog(user, logclient.ACT_REMOVE_USER, self, userCred, true) + } + } + + for _, add := range set.Difference(newU, local).List() { + id := add.(string) + user, ok := userMaps[id] + if ok { + err = self.addUser(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "addUser")) + } + logclient.AddSimpleActionLog(self, logclient.ACT_ADD_USER, user, userCred, true) + logclient.AddSimpleActionLog(user, logclient.ACT_ADD_USER, self, userCred, true) + } + } + + return nil, self.StartCloudgroupSyncUsersTask(ctx, userCred, "") +} + +func (self *SCloudgroup) AllowPerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "set-policies") +} + +// 设置权限组添权限(全量覆盖) +// 权限组状态必须为: available +func (self *SCloudgroup) PerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSetPoliciesInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_GROUP_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not set policies in status %s", self.Status) + } + + policies, err := self.GetCloudpolicies() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + policyMaps := map[string]*SCloudpolicy{} + local := set.New(set.ThreadSafe) + for i := range policies { + local.Add(policies[i].Id) + policyMaps[policies[i].Id] = &policies[i] + } + + newP := set.New(set.ThreadSafe) + for _, policyId := range input.CloudpolicyIds { + _policy, err := CloudpolicyManager.FetchById(policyId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", policyId) + } + return nil, httperrors.NewGeneralError(err) + } + policy := _policy.(*SCloudpolicy) + if policy.Provider != self.Provider { + return nil, httperrors.NewConflictError("policy %s(%s) and group not with same provider", policy.Name, policy.Id) + } + newP.Add(policyId) + policyMaps[policyId] = policy + } + + for _, del := range set.Difference(local, newP).List() { + id := del.(string) + policy, ok := policyMaps[id] + if ok { + err = self.detachPolicy(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "detachPolicy")) + } + logclient.AddSimpleActionLog(self, logclient.ACT_DETACH_POLICY, policy, userCred, true) + } + } + + for _, add := range set.Difference(newP, local).List() { + id := add.(string) + policy, ok := policyMaps[id] + if ok { + err = self.attachPolicy(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "attachPolicy")) + } + logclient.AddSimpleActionLog(self, logclient.ACT_ATTACH_POLICY, policy, userCred, true) + } + } + + return nil, self.StartCloudgroupSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SCloudgroup) StartCloudgroupSyncPoliciesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupSyncPoliciesTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_GROUP_STATUS_SYNC_POLICIES, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudgroup) AllowPerformAttachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "attach-policy") +} + +// 向权限组添加权限 +// 权限组状态必须为: available +func (self *SCloudgroup) PerformAttachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupAttachPolicyInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_GROUP_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not attach policy in status %s", self.Status) + } + + if len(input.CloudpolicyId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudpolicy_id") + } + + _policy, err := CloudpolicyManager.FetchById(input.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", input.CloudpolicyId) + } + return nil, httperrors.NewGeneralError(err) + } + policy := _policy.(*SCloudpolicy) + if policy.Provider != self.Provider { + return nil, httperrors.NewDuplicateResourceError("group and policy not with same provider") + } + + _, err = self.GetCloudpolicy(input.CloudpolicyId) + if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry { + return nil, httperrors.NewDuplicateResourceError("policy %s has aleady in this group", input.CloudpolicyId) + } + + err = self.attachPolicy(input.CloudpolicyId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "attachPolicy")) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_ATTACH_POLICY, policy, userCred, true) + return nil, self.StartCloudgroupSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SCloudgroup) AllowPerformDetachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "detach-policy") +} + +// 从权限组移除权限 +// 权限组状态必须为: available +func (self *SCloudgroup) PerformDetachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupDetachPolicyInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_GROUP_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not detach policy in status %s", self.Status) + } + if len(input.CloudpolicyId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudpolicy_id") + } + _policy, err := CloudpolicyManager.FetchById(input.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", input.CloudpolicyId) + } + return nil, httperrors.NewGeneralError(err) + } + policy := _policy.(*SCloudpolicy) + + _, err = self.GetCloudpolicy(input.CloudpolicyId) + if err != nil && errors.Cause(err) == sql.ErrNoRows { + return nil, nil + } + + err = self.detachPolicy(input.CloudpolicyId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "detachPolicy")) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_DETACH_POLICY, policy, userCred, true) + return nil, self.StartCloudgroupSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SCloudgroup) attachPolicy(policyId string) error { + gp := &SCloudgroupPolicy{} + gp.SetModelManager(CloudgroupPolicyManager, gp) + gp.CloudpolicyId = policyId + gp.CloudgroupId = self.Id + return CloudgroupPolicyManager.TableSpec().Insert(context.Background(), gp) +} + +func (self *SCloudgroup) IsEqual(iPolicies []cloudprovider.ICloudpolicy) (bool, error) { + dbPolicies, err := self.GetCloudpolicies() + if err != nil { + return false, errors.Wrap(err, "GetCloudpolicies") + } + + removed := make([]SCloudpolicy, 0) + commondb := make([]SCloudpolicy, 0) + commonext := make([]cloudprovider.ICloudpolicy, 0) + added := make([]cloudprovider.ICloudpolicy, 0) + err = compare.CompareSets(dbPolicies, iPolicies, &removed, &commondb, &commonext, &added) + if err != nil { + return false, errors.Wrap(err, "CompareSets") + } + return len(iPolicies) == len(commondb), nil +} + +func (self *SCloudgroup) attachPolicyFromCloudpolicy(ctx context.Context, userCred mcclient.TokenCredential, iPolicy cloudprovider.ICloudpolicy) error { + up := &SCloudgroupPolicy{} + up.SetModelManager(CloudgroupPolicyManager, up) + up.CloudgroupId = self.Id + p, err := db.FetchByExternalId(CloudgroupPolicyManager, iPolicy.GetGlobalId()) + if err != nil { + return errors.Wrapf(err, "db.FetchByExternalId(%s)", iPolicy.GetGlobalId()) + } + up.CloudpolicyId = p.GetId() + return CloudgroupPolicyManager.TableSpec().Insert(ctx, up) +} diff --git a/pkg/cloudid/models/cloudgroup_policies.go b/pkg/cloudid/models/cloudgroup_policies.go new file mode 100644 index 0000000000..3861e0f2b1 --- /dev/null +++ b/pkg/cloudid/models/cloudgroup_policies.go @@ -0,0 +1,187 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +// +onecloud:swagger-gen-ignore +type SCloudgroupPolicyManager struct { + SCloudgroupJointsManager + SCloudpolicyResourceBaseManager +} + +var CloudgroupPolicyManager *SCloudgroupPolicyManager + +func init() { + db.InitManager(func() { + CloudgroupPolicyManager = &SCloudgroupPolicyManager{ + SCloudgroupJointsManager: NewCloudgroupJointsManager( + SCloudgroupPolicy{}, + "cloudgroup_policies_tbl", + "cloudgroup_policy", + "cloudgroup_policies", + CloudpolicyManager, + ), + } + CloudgroupPolicyManager.SetVirtualObject(CloudgroupPolicyManager) + CloudgroupPolicyManager.TableSpec().AddIndex(true, "cloudgroup_id", "cloudpolicy_id") + }) + +} + +type SCloudgroupPolicy struct { + SCloudgroupJointsBase + SCloudpolicyResourceBase +} + +func (manager *SCloudgroupPolicyManager) GetSlaveFieldName() string { + return "cloudpolicy_id" +} + +func (manager *SCloudgroupPolicyManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (self *SCloudgroupPolicy) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +// +onecloud:swagger-gen-ignore +func (manager *SCloudgroupPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewNotSupportedError("Not Supported") +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudgroupPolicy) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.SCloudgroupJointsBase.CustomizeDelete(ctx, userCred, query, data) +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudgroupPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewNotSupportedError("Not Supported") +} + +func (joint *SCloudgroupPolicy) Master() db.IStandaloneModel { + return db.JointMaster(joint) +} + +func (joint *SCloudgroupPolicy) Slave() db.IStandaloneModel { + return db.JointSlave(joint) +} + +// 用户组中权限详情 +func (self *SCloudgroupPolicy) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudgroupPolicyDetails, error) { + return api.CloudgroupPolicyDetails{}, nil +} + +func (manager *SCloudgroupPolicyManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudgroupPolicyDetails { + rows := make([]api.CloudgroupPolicyDetails, len(objs)) + + groupRows := manager.SCloudgroupJointsManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + policyRows := manager.SCloudpolicyResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.CloudgroupPolicyDetails{ + CloudgroupJointResourceDetails: groupRows[i], + CloudpolicyResourceDetails: policyRows[i], + } + } + + return rows +} + +func (self *SCloudgroupPolicy) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +func (self *SCloudgroupPolicy) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DetachJoint(ctx, userCred, self) +} + +// 用户组中权限列表 +func (manager *SCloudgroupPolicyManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudgroupPolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SCloudgroupJointsManager.ListItemFilter(ctx, q, userCred, query.CloudgroupJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudgroupJointsManager.ListItemFilter") + } + + q, err = manager.SCloudpolicyResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudpolicyResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +func (manager *SCloudgroupPolicyManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudgroupPolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SCloudgroupJointsManager.OrderByExtraFields(ctx, q, userCred, query.CloudgroupJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudgroupJointsManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SCloudgroupPolicyManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SCloudgroupJointsManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SCloudgroupJointsManager.ListItemExportKeys") + } + + return q, nil +} diff --git a/pkg/cloudid/models/cloudgroup_users.go b/pkg/cloudid/models/cloudgroup_users.go new file mode 100644 index 0000000000..e42589fc9b --- /dev/null +++ b/pkg/cloudid/models/cloudgroup_users.go @@ -0,0 +1,204 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +// +onecloud:swagger-gen-ignore +type SCloudgroupUserManager struct { + SCloudgroupJointsManager + SClouduserResourceBaseManager +} + +var CloudgroupUserManager *SCloudgroupUserManager + +func init() { + db.InitManager(func() { + CloudgroupUserManager = &SCloudgroupUserManager{ + SCloudgroupJointsManager: NewCloudgroupJointsManager( + SCloudgroupUser{}, + "cloudgroup_users_tbl", + "cloudgroup_user", + "cloudgroup_users", + ClouduserManager, + ), + } + CloudgroupUserManager.SetVirtualObject(CloudgroupUserManager) + CloudgroupUserManager.TableSpec().AddIndex(true, "cloudgroup_id", "clouduser_id") + }) + +} + +type SCloudgroupUser struct { + SCloudgroupJointsBase + + SClouduserResourceBase +} + +func (manager *SCloudgroupUserManager) GetSlaveFieldName() string { + return "clouduser_id" +} + +func (manager *SCloudgroupUserManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (self *SCloudgroupUser) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +// +onecloud:swagger-gen-ignore +func (manager *SCloudgroupUserManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewNotSupportedError("Not Supported") +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudgroupUser) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.SCloudgroupJointsBase.CustomizeDelete(ctx, userCred, query, data) +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudgroupUser) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewNotSupportedError("Not Supported") +} + +func (joint *SCloudgroupUser) Master() db.IStandaloneModel { + return db.JointMaster(joint) +} + +func (joint *SCloudgroupUser) Slave() db.IStandaloneModel { + return db.JointSlave(joint) +} + +// 获取用户组中用户详情 +func (self *SCloudgroupUser) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudgroupUserDetails, error) { + return api.CloudgroupUserDetails{}, nil +} + +func (manager *SCloudgroupUserManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudgroupUserDetails { + rows := make([]api.CloudgroupUserDetails, len(objs)) + + groupRows := manager.SCloudgroupJointsManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + userRows := manager.SClouduserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.CloudgroupUserDetails{ + CloudgroupJointResourceDetails: groupRows[i], + ClouduserResourceDetails: userRows[i], + } + } + + return rows +} + +func (self *SCloudgroupUser) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +func (self *SCloudgroupUser) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DetachJoint(ctx, userCred, self) +} + +// 用户组中用户列表 +func (manager *SCloudgroupUserManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudgroupUserListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SCloudgroupJointsManager.ListItemFilter(ctx, q, userCred, query.CloudgroupJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudgroupJointsManager.ListItemFilter") + } + + q, err = manager.SClouduserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ClouduserResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +func (manager *SCloudgroupUserManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudgroupUserListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SCloudgroupJointsManager.OrderByExtraFields(ctx, q, userCred, query.CloudgroupJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudgroupJointsManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SCloudgroupUserManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SCloudgroupJointsManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SCloudgroupJointsManager.ListItemExportKeys") + } + + return q, nil +} + +/* +func (manager *SCloudgroupUserManager) newFromCloudgroupUser(ctx context.Context, userCred mcclient.TokenCredential, iUser cloudprovider.IClouduser, group *SCloudgroup) error { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + user := SCloudgroupUser{} + user.SetModelManager(manager, &user) + user.CloudgroupId = group.Id + u, err := group.GetClouduserByExternalId(iUser.GetGlobalId()) + if err != nil { + return errors.Wrapf(err, "GetClouduserByExternalId(%s)", iUser.GetGlobalId()) + } + user.ClouduserId = u.GetId() + return manager.TableSpec().Insert(&user) +}*/ diff --git a/pkg/cloudid/models/cloudgroupcaches.go b/pkg/cloudid/models/cloudgroupcaches.go new file mode 100644 index 0000000000..16ee60cf73 --- /dev/null +++ b/pkg/cloudid/models/cloudgroupcaches.go @@ -0,0 +1,408 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/rand" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudgroupcacheManager struct { + db.SStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SCloudaccountResourceBaseManager +} + +var CloudgroupcacheManager *SCloudgroupcacheManager + +func init() { + CloudgroupcacheManager = &SCloudgroupcacheManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SCloudgroupcache{}, + "cloudgroupcaches_tbl", + "cloudgroupcache", + "cloudgroupcaches", + ), + } + CloudgroupcacheManager.SetVirtualObject(CloudgroupcacheManager) +} + +type SCloudgroupcache struct { + db.SStatusStandaloneResourceBase + db.SExternalizedResourceBase + SCloudaccountResourceBase + + // 用户组Id + CloudgroupId string `width:"36" charset:"ascii" nullable:"true" list:"user" index:"true" json:"cloudgroup_id"` +} + +func (manager *SCloudgroupcacheManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return db.IsDomainAllowList(userCred, manager) +} + +// 公有云权限组缓存 +func (manager *SCloudgroupcacheManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudgroupcacheListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, err + } + + q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput) + if err != nil { + return nil, err + } + + if len(query.CloudgroupId) > 0 { + _, err = CloudgroupManager.FetchById(query.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", query.CloudgroupId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "CloudgroupManager.FetchById")) + } + q = q.Equals("cloudgroup_id", query.CloudgroupId) + } + + return q, nil +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudgroupcache) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupUpdateInput) (api.CloudgroupUpdateInput, error) { + return input, httperrors.NewNotSupportedError("Not support") +} + +// +onecloud:swagger-gen-ignore +func (manager *SCloudgroupcacheManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.CloudgroupcacheCreateInput) (api.CloudgroupcacheCreateInput, error) { + return input, httperrors.NewNotSupportedError("Not support") +} + +// 删除权限组缓存 +func (self *SCloudgroupcache) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartCloudgroupcacheDeleteTask(ctx, userCred, "") +} + +func (self *SCloudgroupcache) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SCloudgroupcache) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SStatusStandaloneResourceBase.Delete(ctx, userCred) +} + +func (manager *SCloudgroupcacheManager) newFromCloudgroup(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup, group *SCloudgroup, cloudaccountId string) error { + cache := &SCloudgroupcache{} + cache.SetModelManager(manager, cache) + cache.CloudgroupId = group.Id + cache.Name = iGroup.GetName() + cache.Description = iGroup.GetDescription() + cache.Status = api.CLOUD_GROUP_STATUS_AVAILABLE + cache.ExternalId = iGroup.GetGlobalId() + cache.CloudaccountId = cloudaccountId + return manager.TableSpec().Insert(ctx, cache) +} + +func (self *SCloudgroupcache) syncWithCloudgrup(ctx context.Context, userCred mcclient.TokenCredential, iGroup cloudprovider.ICloudgroup) error { + _, err := db.Update(self, func() error { + self.Name = iGroup.GetName() + self.Description = iGroup.GetDescription() + self.Status = api.CLOUD_GROUP_STATUS_AVAILABLE + return nil + }) + return err +} + +func (manager *SCloudgroupcacheManager) Register(group *SCloudgroup, account *SCloudaccount) (*SCloudgroupcache, error) { + q := manager.Query().Equals("cloudgroup_id", group.Id).Equals("cloudaccount_id", account.Id) + count, err := q.CountWithError() + if err != nil { + return nil, errors.Wrap(err, "CountWithError") + } + if count > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + cache := &SCloudgroupcache{} + cache.SetModelManager(manager, cache) + if count < 1 { + cache.Name = group.Name + cache.Description = group.Description + cache.Status = api.CLOUD_GROUP_CACHE_STATUS_CREATING + cache.CloudgroupId = group.Id + cache.CloudaccountId = account.Id + return cache, manager.TableSpec().Insert(context.Background(), cache) + } + err = q.First(cache) + if err != nil { + return nil, errors.Wrap(err, "q.First") + } + return cache, nil +} + +// 获取权限组缓存详情 +func (self *SCloudgroupcache) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudgroupcacheDetails, error) { + return api.CloudgroupcacheDetails{}, nil +} + +func (manager *SCloudgroupcacheManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudgroupcacheDetails { + rows := make([]api.CloudgroupcacheDetails, len(objs)) + statusRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.CloudgroupcacheDetails{ + StatusStandaloneResourceDetails: statusRows[i], + CloudaccountResourceDetails: acRows[i], + } + } + return rows +} + +func (self *SCloudgroupcache) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "syncstatus") +} + +// 同步权限组缓存状态 +func (self *SCloudgroupcache) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudgroupSyncstatusInput) (jsonutils.JSONObject, error) { + return nil, self.StartCloudgroupcacheSyncstatusTask(ctx, userCred, "") +} + +func (self *SCloudgroupcache) StartCloudgroupcacheSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupcacheSyncstatusTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_GROUP_CACHE_STATUS_SYNC_STATUS, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudgroupcache) StartCloudgroupcacheDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "CloudgroupcacheDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_GROUP_STATUS_DELETING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SCloudgroupcache) GetOrCreateICloudgroup(ctx context.Context, userCred mcclient.TokenCredential) (cloudprovider.ICloudgroup, error) { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + if len(self.ExternalId) > 0 { + iGroup, err := self.GetICloudgroup() + if err == nil { + return iGroup, nil + } + if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, errors.Wrap(err, "GetICloudgroup") + } + } + account, err := self.GetCloudaccount() + if err != nil { + return nil, errors.Wrap(err, "GetCloudaccount") + } + + provider, err := account.GetProvider() + if err != nil { + return nil, errors.Wrap(err, "account.GetProvider") + } + + randomString := func(prefix string, length int) string { + return fmt.Sprintf("%s-%s", prefix, rand.String(length)) + } + + groupName := self.Name + for i := 2; i < 30; i++ { + _, err := provider.GetICloudgroupByName(groupName) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + break + } + return nil, errors.Wrapf(err, "GetICloudgroupByName(%s)", groupName) + } + groupName = randomString(self.Name, i) + } + + iGroup, err := provider.CreateICloudgroup(groupName, self.Description) + if err != nil { + logclient.AddSimpleActionLog(self, logclient.ACT_CREATE, err, userCred, false) + return nil, errors.Wrap(err, "CreateICloudgroup") + } + _, err = db.Update(self, func() error { + self.ExternalId = iGroup.GetGlobalId() + self.Status = api.CLOUD_GROUP_STATUS_AVAILABLE + return nil + }) + return iGroup, err +} + +func (self *SCloudgroupcache) GetCloudgroup() (*SCloudgroup, error) { + group, err := CloudgroupManager.FetchById(self.CloudgroupId) + if err != nil { + return nil, errors.Wrapf(err, "FetchById(%s)", self.CloudgroupId) + } + return group.(*SCloudgroup), nil +} + +func (self *SCloudgroupcache) GetICloudgroup() (cloudprovider.ICloudgroup, error) { + account, err := self.GetCloudaccount() + if err != nil { + return nil, errors.Wrap(err, "self.GetCloudaccount") + } + provider, err := account.GetProvider() + if err != nil { + return nil, errors.Wrap(err, "account.GetProvider") + } + return provider.GetICloudgroupByName(self.Name) +} + +// 将本地的权限推送到云上(覆盖云上设置) +func (self *SCloudgroupcache) SyncCloudpoliciesForCloud(ctx context.Context) (result compare.SyncResult, err error) { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + iGroup, err := self.GetICloudgroup() + if err != nil { + return result, errors.Wrap(err, "GetICloudgroup") + } + iPolicies, err := iGroup.GetISystemCloudpolicies() + if err != nil { + return result, errors.Wrap(err, "GetISystemCloudpolicies") + } + group, err := self.GetCloudgroup() + if err != nil { + return result, errors.Wrap(err, "GetCloudgroup") + } + dbPolicies, err := group.GetCloudpolicies() + if err != nil { + return result, errors.Wrap(err, "GetCloudpolicies") + } + + added := make([]SCloudpolicy, 0) + commondb := make([]SCloudpolicy, 0) + commonext := make([]cloudprovider.ICloudpolicy, 0) + removed := make([]cloudprovider.ICloudpolicy, 0) + + err = compare.CompareSets(dbPolicies, iPolicies, &added, &commondb, &commonext, &removed) + if err != nil { + return result, errors.Wrap(err, "compare.CompareSets") + } + + for i := 0; i < len(removed); i++ { + err = iGroup.DetachSystemPolicy(removed[i].GetGlobalId()) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + result.UpdateCnt = len(commondb) + + for i := 0; i < len(added); i++ { + if added[i].PolicyType == api.CLOUD_POLICY_TYPE_SYSTEM { + err = iGroup.AttachSystemPolicy(added[i].ExternalId) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + } + return result, nil +} + +// 将本地的用户推送到云上(覆盖云上设置) +func (self *SCloudgroupcache) SyncCloudusersForCloud(ctx context.Context) (result compare.SyncResult, err error) { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + iGroup, err := self.GetICloudgroup() + if err != nil { + return result, errors.Wrap(err, "GetICloudgroup") + } + iUsers, err := iGroup.GetICloudusers() + if err != nil { + return result, errors.Wrap(err, "GetICloudusers") + } + group, err := self.GetCloudgroup() + if err != nil { + return result, errors.Wrap(err, "GetCloudgroup") + } + dbUsers, err := group.GetCloudusers() + if err != nil { + return result, errors.Wrap(err, "GetCloudusers") + } + + added := make([]SClouduser, 0) + commondb := make([]SClouduser, 0) + commonext := make([]cloudprovider.IClouduser, 0) + removed := make([]cloudprovider.IClouduser, 0) + + err = compare.CompareSets(dbUsers, iUsers, &added, &commondb, &commonext, &removed) + if err != nil { + return result, errors.Wrap(err, "compare.CompareSets") + } + + for i := 0; i < len(removed); i++ { + err = iGroup.RemoveUser(removed[i].GetName()) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + result.UpdateCnt = len(commondb) + + for i := 0; i < len(added); i++ { + err = iGroup.AddUser(added[i].GetName()) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + return result, nil +} diff --git a/pkg/cloudid/models/cloudgroupjoints.go b/pkg/cloudid/models/cloudgroupjoints.go new file mode 100644 index 0000000000..f61e107c95 --- /dev/null +++ b/pkg/cloudid/models/cloudgroupjoints.go @@ -0,0 +1,186 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudgroupJointsManager struct { + db.SJointResourceBaseManager +} + +func NewCloudgroupJointsManager(dt interface{}, tableName string, keyword string, keywordPlural string, slave db.IVirtualModelManager) SCloudgroupJointsManager { + return SCloudgroupJointsManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + dt, + tableName, + keyword, + keywordPlural, + CloudgroupManager, + slave, + ), + } +} + +type SCloudgroupJointsBase struct { + db.SJointResourceBase + + // 用户组Id + CloudgroupId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" index:"true" json:"cloudgroup_id"` +} + +func (self *SCloudgroupJointsBase) getCloudgroup() (*SCloudgroup, error) { + group, err := CloudgroupManager.FetchById(self.CloudgroupId) + if err != nil { + return nil, errors.Wrap(err, "FetchById") + } + return group.(*SCloudgroup), nil +} + +func (manager *SCloudgroupJointsManager) GetMasterFieldName() string { + return "cloudgroup_id" +} + +func (self *SCloudgroupJointsBase) GetExtraDetails( + ctx context.Context, + groupCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudgroupJointResourceDetails, error) { + return api.CloudgroupJointResourceDetails{}, nil +} + +func (manager *SCloudgroupJointsManager) FetchCustomizeColumns( + ctx context.Context, + groupCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudgroupJointResourceDetails { + rows := make([]api.CloudgroupJointResourceDetails, len(objs)) + + jointRows := manager.SJointResourceBaseManager.FetchCustomizeColumns(ctx, groupCred, query, objs, fields, isList) + + groupIds := make([]string, len(rows)) + for i := range rows { + rows[i] = api.CloudgroupJointResourceDetails{ + JointResourceBaseDetails: jointRows[i], + } + var base *SCloudgroupJointsBase + reflectutils.FindAnonymouStructPointer(objs[i], &base) + if base != nil && len(base.CloudgroupId) > 0 { + groupIds[i] = base.CloudgroupId + } + } + + groupIdMaps, err := db.FetchIdNameMap2(CloudgroupManager, groupIds) + if err != nil { + log.Errorf("db.FetchIdNameMap2 fail %s", err) + return rows + } + + for i := range rows { + if name, ok := groupIdMaps[groupIds[i]]; ok { + rows[i].Cloudgroup = name + } + } + + return rows +} + +func (manager *SCloudgroupJointsManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + groupCred mcclient.TokenCredential, + query api.CloudgroupJointsListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SJointResourceBaseManager.ListItemFilter(ctx, q, groupCred, query.JointResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SJointResourceBaseManager.ListItemFilter") + } + + if len(query.Cloudgroup) > 0 { + group, err := CloudgroupManager.FetchByIdOrName(nil, query.Cloudgroup) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", query.Cloudgroup) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("cloudgroup_id", group.GetId()) + } + + return q, nil +} + +func (manager *SCloudgroupJointsManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + groupCred mcclient.TokenCredential, + query api.CloudgroupJointsListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SJointResourceBaseManager.OrderByExtraFields(ctx, q, groupCred, query.JointResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SJointResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (self *SCloudgroupJointsBase) ValidateUpdateData( + ctx context.Context, + groupCred mcclient.TokenCredential, + query jsonutils.JSONObject, + input api.CloudgroupJointBaseUpdateInput, +) (api.CloudgroupJointBaseUpdateInput, error) { + var err error + input.JointResourceBaseUpdateInput, err = self.SJointResourceBase.ValidateUpdateData(ctx, groupCred, query, input.JointResourceBaseUpdateInput) + if err != nil { + return input, errors.Wrap(err, "SJointResourceBase.ValidateUpdateData") + } + return input, nil +} + +func (manager *SCloudgroupJointsManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + groupCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SJointResourceBaseManager.ListItemExportKeys(ctx, q, groupCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SJointResourceBaseManager.ListItemExportKeys") + } + return q, nil +} diff --git a/pkg/cloudid/models/cloudpolicy.go b/pkg/cloudid/models/cloudpolicy.go new file mode 100644 index 0000000000..2f9ff91e81 --- /dev/null +++ b/pkg/cloudid/models/cloudpolicy.go @@ -0,0 +1,280 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudpolicyManager struct { + db.SStatusStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager +} + +var CloudpolicyManager *SCloudpolicyManager + +func init() { + CloudpolicyManager = &SCloudpolicyManager{ + SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager( + SCloudpolicy{}, + "cloudpolicy_tbl", + "cloudpolicy", + "cloudpolicies", + ), + } + CloudpolicyManager.SetVirtualObject(CloudpolicyManager) +} + +type SCloudpolicy struct { + db.SStatusStandaloneResourceBase + db.SExternalizedResourceBase + + // 权限类型 + // + // | 权限类型 | 说明 | + // |---------------|----------------------| + // | system | 平台内置权限 | + // | custom | 目前不支持 | + PolicyType string `width:"16" charset:"ascii" list:"domain" default:"custom"` + + // 平台 + Provider string `width:"64" charset:"ascii" list:"domain"` +} + +func (manager *SCloudpolicyManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (manager *SCloudpolicyManager) GetIVirtualModelManager() db.IVirtualModelManager { + return manager.GetVirtualObject().(db.IVirtualModelManager) +} + +func (manager *SCloudpolicyManager) GetResourceCount() ([]db.SScopeResourceCount, error) { + return nil, nil +} + +// 公有云权限列表 +func (manager *SCloudpolicyManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudpolicyListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, err + } + + if len(query.Provider) > 0 { + q = q.In("provider", query.Provider) + } + + if len(query.ClouduserId) > 0 { + _, err = ClouduserManager.FetchById(query.ClouduserId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("clouduser", query.ClouduserId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "ClouduserManager.FetchById")) + } + sq := ClouduserPolicyManager.Query("cloudpolicy_id").Equals("clouduser_id", query.ClouduserId) + q = q.In("id", sq.SubQuery()) + } + + if len(query.CloudgroupId) > 0 { + _, err = CloudgroupManager.FetchById(query.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", query.CloudgroupId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "CloudgroupManager.FetchById")) + } + sq := CloudgroupPolicyManager.Query("cloudpolicy_id").Equals("cloudgroup_id", query.CloudgroupId) + q = q.In("id", sq.SubQuery()) + } + + return q, nil +} + +func (manager *SCloudpolicyManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +// +onecloud:swagger-gen-ignore +func (manager *SCloudpolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.CloudpolicyCreateInput) (api.CloudpolicyCreateInput, error) { + return input, httperrors.NewNotImplementedError("Not Implement") +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudpolicy) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudpolicyUpdateInput) (api.CloudpolicyUpdateInput, error) { + return input, nil +} + +// +onecloud:swagger-gen-ignore +func (self *SCloudpolicy) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.SStatusStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data) +} + +func (manager *SCloudpolicyManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudpolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusStandaloneResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (manager *SCloudpolicyManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (self *SCloudpolicy) AllowPerformAssignGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "assign-group") +} + +// 将权限加入权限组 +func (self *SCloudpolicy) PerformAssignGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudpolicyAssignGroupInput) (jsonutils.JSONObject, error) { + if len(input.CloudgroupId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudgroup_id") + } + _group, err := CloudgroupManager.FetchById(input.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", input.CloudgroupId) + } + return nil, httperrors.NewGeneralError(err) + } + group := _group.(*SCloudgroup) + if self.Provider != group.Provider { + return nil, httperrors.NewConflictError("policy and group not with same provider") + } + err = group.attachPolicy(self.Id) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + return nil, group.StartCloudgroupSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SCloudpolicy) AllowPerformRevokeGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "revoke-group") +} + +// 将权限从权限组中移除 +func (self *SCloudpolicy) PerformRevokeGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.CloudpolicyRevokeGroupInput) (jsonutils.JSONObject, error) { + if len(input.CloudgroupId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudgroup_id") + } + _group, err := CloudgroupManager.FetchById(input.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", input.CloudgroupId) + } + return nil, httperrors.NewGeneralError(err) + } + group := _group.(*SCloudgroup) + if self.Provider != group.Provider { + return nil, httperrors.NewConflictError("policy and group not with same provider") + } + err = group.detachPolicy(self.Id) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + return nil, group.StartCloudgroupSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SCloudpolicy) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +// 获取公有云权限详情 +func (self *SCloudpolicy) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.CloudpolicyDetails, error) { + return api.CloudpolicyDetails{}, nil +} + +func (manager *SCloudpolicyManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudpolicyDetails { + rows := make([]api.CloudpolicyDetails, len(objs)) + statusRows := manager.SStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.CloudpolicyDetails{ + StatusStandaloneResourceDetails: statusRows[i], + } + } + return rows +} + +func (self *SCloudpolicy) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return false +} + +func (self *SCloudpolicy) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (manager *SCloudpolicyManager) newFromCloudpolicy(ctx context.Context, userCred mcclient.TokenCredential, iPolicy cloudprovider.ICloudpolicy, provider string) error { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + policy := &SCloudpolicy{} + policy.SetModelManager(manager, policy) + policy.Name = iPolicy.GetName() + policy.Status = api.CLOUD_POLICY_STATUS_AVAILABLE + policy.PolicyType = api.CLOUD_POLICY_TYPE_SYSTEM + policy.Provider = provider + policy.ExternalId = iPolicy.GetGlobalId() + policy.Description = iPolicy.GetDescription() + return manager.TableSpec().Insert(ctx, policy) +} + +func (self *SCloudpolicy) SyncWithCloudpolicy(ctx context.Context, userCred mcclient.TokenCredential, iPolicy cloudprovider.ICloudpolicy) error { + _, err := db.Update(self, func() error { + self.Name = iPolicy.GetName() + self.Description = iPolicy.GetDescription() + self.Status = api.CLOUD_POLICY_STATUS_AVAILABLE + return nil + }) + return err +} diff --git a/pkg/cloudid/models/cloudpolicyresource.go b/pkg/cloudid/models/cloudpolicyresource.go new file mode 100644 index 0000000000..3ca5835055 --- /dev/null +++ b/pkg/cloudid/models/cloudpolicyresource.go @@ -0,0 +1,83 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudpolicyResourceBaseManager struct { +} + +type SCloudpolicyResourceBase struct { + // 权限Id + CloudpolicyId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required" json:"cloudpolicy_id"` +} + +func (manager *SCloudpolicyResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, policyCred mcclient.TokenCredential, query api.CloudpolicyResourceListInput) (*sqlchemy.SQuery, error) { + if len(query.Cloudpolicy) > 0 { + policy, err := CloudpolicyManager.FetchByIdOrName(nil, query.Cloudpolicy) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", query.Cloudpolicy) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("cloudpolicy_id", policy.GetId()) + } + return q, nil +} + +func (manager *SCloudpolicyResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + policyCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudpolicyResourceDetails { + rows := make([]api.CloudpolicyResourceDetails, len(objs)) + policyIds := make([]string, len(objs)) + for i := range objs { + var base *SCloudpolicyResourceBase + err := reflectutils.FindAnonymouStructPointer(objs[i], &base) + if err != nil { + log.Errorf("Cannot find SCloudpolicyResourceBase in %#v: %s", objs[i], err) + } else if base != nil && len(base.CloudpolicyId) > 0 { + policyIds[i] = base.CloudpolicyId + } + } + policyMaps, err := db.FetchIdNameMap2(CloudpolicyManager, policyIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail %v", err) + return rows + } + for i := range rows { + rows[i].Cloudpolicy, _ = policyMaps[policyIds[i]] + } + return rows +} diff --git a/pkg/cloudid/models/cloudprovider.go b/pkg/cloudid/models/cloudprovider.go new file mode 100644 index 0000000000..2058f53ce9 --- /dev/null +++ b/pkg/cloudid/models/cloudprovider.go @@ -0,0 +1,168 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudid/options" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +// +onecloud:swagger-gen-ignore +type SCloudproviderManager struct { + db.SStandaloneResourceBaseManager +} + +var CloudproviderManager *SCloudproviderManager + +func init() { + CloudproviderManager = &SCloudproviderManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SCloudprovider{}, + "cloudproviders_tbl", + "cloudprovider", + "cloudproviders", + ), + } + CloudproviderManager.SetVirtualObject(CloudproviderManager) +} + +type SCloudprovider struct { + db.SStandaloneResourceBase + + Provider string `width:"64" charset:"ascii" list:"domain"` + CloudaccountId string `width:"36" charset:"ascii" nullable:"false" list:"user"` +} + +func (self *SCloudprovider) syncRemoveClouduser(ctx context.Context, userCred mcclient.TokenCredential) error { + users, err := self.getCloudusers() + if err != nil { + return errors.Wrap(err, "getCloudusers") + } + for i := range users { + err = users[i].RealDelete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "RealDelete user %s(%s)", users[i].Name, users[i].Id) + } + } + return self.Delete(ctx, userCred) +} + +func (manager *SCloudproviderManager) newFromRegionProvider(ctx context.Context, userCred mcclient.TokenCredential, provider SCloudprovider) error { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + return manager.TableSpec().Insert(ctx, &provider) +} + +func (self *SCloudprovider) syncWithRegionProvider(ctx context.Context, userCred mcclient.TokenCredential, provider SCloudprovider) error { + _, err := db.UpdateWithLock(ctx, self, func() error { + self.Name = provider.Name + return nil + }) + return err +} + +func (self SCloudprovider) GetGlobalId() string { + return self.Id +} + +func (self SCloudprovider) GetExternalId() string { + return self.Id +} + +func (self SCloudprovider) GetCloudproviderId() string { + return self.Id +} + +func (manager *SCloudproviderManager) FetchProvider(ctx context.Context, id string) (*SCloudprovider, error) { + provider, err := manager.FetchById(id) + if err != nil { + if err == sql.ErrNoRows { + session := auth.GetAdminSession(context.Background(), options.Options.Region, "") + result, err := modules.Cloudproviders.Get(session, id, nil) + if err != nil { + return nil, errors.Wrap(err, "Cloudproviders.Get") + } + _provider := &SCloudprovider{} + _provider.SetModelManager(manager, _provider) + err = result.Unmarshal(_provider) + if err != nil { + return nil, errors.Wrap(err, "result.Unmarshal") + } + + lockman.LockRawObject(ctx, manager.KeywordPlural(), id) + defer lockman.ReleaseRawObject(ctx, manager.KeywordPlural(), id) + return _provider, manager.TableSpec().InsertOrUpdate(ctx, _provider) + } + return nil, errors.Wrap(err, "manager.FetchById") + } + return provider.(*SCloudprovider), nil +} + +func (self *SCloudprovider) getCloudDelegate(ctx context.Context) (*SCloudDelegate, error) { + s := auth.GetAdminSession(ctx, options.Options.Region, "") + result, err := modules.Cloudproviders.Get(s, self.Id, nil) + if err != nil { + return nil, errors.Wrap(err, "Cloudproviders.Get") + } + delegate := &SCloudDelegate{} + err = result.Unmarshal(delegate) + if err != nil { + return nil, errors.Wrap(err, "result.Unmarshal") + } + return delegate, nil +} + +func (self *SCloudprovider) GetProvider() (cloudprovider.ICloudProvider, error) { + delegate, err := self.getCloudDelegate(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "getCloudproviderDelegate") + } + return delegate.GetProvider() +} + +func (self *SCloudprovider) GetProviderFactory() (cloudprovider.ICloudProviderFactory, error) { + return cloudprovider.GetProviderFactory(self.Provider) +} + +func (self *SCloudprovider) getCloudusers() ([]SClouduser, error) { + users := []SClouduser{} + q := ClouduserManager.Query().Equals("cloudprovider_id", self.Id) + err := db.FetchModelObjects(ClouduserManager, q, &users) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return users, nil +} + +func (self *SCloudprovider) getAvailableUsers(cloudproviderId string) ([]SClouduser, error) { + users := []SClouduser{} + q := ClouduserManager.Query().Equals("status", api.CLOUD_USER_STATUS_AVAILABLE).Equals("cloudprovider_id", self.Id) + err := db.FetchModelObjects(ClouduserManager, q, &users) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return users, nil +} diff --git a/pkg/cloudid/models/cloudproviderresource.go b/pkg/cloudid/models/cloudproviderresource.go new file mode 100644 index 0000000000..3ad29afa7d --- /dev/null +++ b/pkg/cloudid/models/cloudproviderresource.go @@ -0,0 +1,83 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SCloudproviderResourceBaseManager struct { +} + +type SCloudproviderResourceBase struct { + // 子订阅Id + CloudproviderId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional" json:"cloudprovider_id"` +} + +func (manager *SCloudproviderResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.CloudproviderResourceListInput) (*sqlchemy.SQuery, error) { + if len(query.Cloudprovider) > 0 { + provider, err := CloudproviderManager.FetchByIdOrName(nil, query.Cloudprovider) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudprovider", query.Cloudprovider) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("cloudprovider_id", provider.GetId()) + } + return q, nil +} + +func (manager *SCloudproviderResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.CloudproviderResourceDetails { + rows := make([]api.CloudproviderResourceDetails, len(objs)) + providerIds := make([]string, len(objs)) + for i := range objs { + var base *SCloudproviderResourceBase + err := reflectutils.FindAnonymouStructPointer(objs[i], &base) + if err != nil { + log.Errorf("Cannot find SCloudproviderResourceBase in %#v: %s", objs[i], err) + } else if base != nil && len(base.CloudproviderId) > 0 { + providerIds[i] = base.CloudproviderId + } + } + providerMaps, err := db.FetchIdNameMap2(CloudproviderManager, providerIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail %v", err) + return rows + } + for i := range rows { + rows[i].Cloudprovider, _ = providerMaps[providerIds[i]] + } + return rows +} diff --git a/pkg/cloudid/models/clouduser.go b/pkg/cloudid/models/clouduser.go new file mode 100644 index 0000000000..6b6e3c9a8a --- /dev/null +++ b/pkg/cloudid/models/clouduser.go @@ -0,0 +1,1250 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "gopkg.in/fatih/set.v0" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/logclient" + "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SClouduserManager struct { + db.SStatusUserResourceBaseManager + db.SExternalizedResourceBaseManager + SCloudaccountResourceBaseManager + SCloudproviderResourceBaseManager +} + +var ClouduserManager *SClouduserManager + +func init() { + ClouduserManager = &SClouduserManager{ + SStatusUserResourceBaseManager: db.NewStatusUserResourceBaseManager( + SClouduser{}, + "cloudusers_tbl", + "clouduser", + "cloudusers", + ), + } + ClouduserManager.NameRequireAscii = false + ClouduserManager.SetVirtualObject(ClouduserManager) +} + +type SClouduser struct { + db.SStatusUserResourceBase + db.SExternalizedResourceBase + SCloudproviderResourceBase + SCloudaccountResourceBase + + Secret string `length:"0" charset:"ascii" nullable:"true" list:"user" create:"domain_optional"` + // 是否可以控制台登录 + IsConsoleLogin tristate.TriState `nullable:"false" default:"false" list:"user" create:"optional"` + // 手机号码 + MobilePhone string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"domain_optional"` + // 邮箱地址 + Email string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"domain_optional"` +} + +func (manager *SClouduserManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return true +} + +func (manager *SClouduserManager) GetIVirtualModelManager() db.IVirtualModelManager { + return manager.GetVirtualObject().(db.IVirtualModelManager) +} + +// 公有云用户列表 +func (manager *SClouduserManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ClouduserListInput) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusUserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StatusUserResourceListInput) + if err != nil { + return nil, err + } + + q, err = manager.SCloudaccountResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudaccountResourceListInput) + if err != nil { + return nil, err + } + + q, err = manager.SCloudproviderResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudproviderResourceListInput) + if err != nil { + return nil, err + } + + if len(query.CloudpolicyId) > 0 { + _, err = CloudpolicyManager.FetchById(query.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", query.CloudpolicyId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "CloudpolicyManager.FetchById")) + } + sq := ClouduserPolicyManager.Query("clouduser_id").Equals("cloudpolicy_id", query.CloudpolicyId) + q = q.In("id", sq.SubQuery()) + } + + if len(query.CloudgroupId) > 0 { + _, err = CloudgroupManager.FetchById(query.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", query.CloudgroupId) + } + return q, httperrors.NewGeneralError(errors.Wrap(err, "CloudgroupManager.FetchById")) + } + sq := CloudgroupUserManager.Query("clouduser_id").Equals("cloudgroup_id", query.CloudgroupId) + q = q.In("id", sq.SubQuery()) + } + + return q, nil +} + +// +onecloud:swagger-gen-ignore +func (self *SClouduser) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserUpdateInput) (api.ClouduserUpdateInput, error) { + return input, nil +} + +func (manager *SClouduserManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ClouduserListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusUserResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StatusUserResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStatusUserResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SClouduserManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SStatusUserResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +// 获取公有云用户详情 +func (self *SClouduser) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.ClouduserDetails, error) { + return api.ClouduserDetails{}, nil +} + +func (manager *SClouduserManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ClouduserDetails { + rows := make([]api.ClouduserDetails, len(objs)) + userRows := manager.SStatusUserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + acRows := manager.SCloudaccountResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + apRows := manager.SCloudproviderResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.ClouduserDetails{ + StatusUserResourceDetails: userRows[i], + CloudaccountResourceDetails: acRows[i], + CloudproviderResourceDetails: apRows[i], + } + user := objs[i].(*SClouduser) + policies, _ := user.GetCloudpolicies() + for _, policy := range policies { + rows[i].Cloudpolicies = append(rows[i].Cloudpolicies, api.SCloudIdBaseResource{Id: policy.Id, Name: policy.Name}) + } + rows[i].CloudpolicyCount = len(policies) + groups, _ := user.GetCloudgroups() + for _, group := range groups { + rows[i].Cloudgroups = append(rows[i].Cloudpolicies, api.SCloudIdBaseResource{Id: group.Id, Name: group.Name}) + } + rows[i].CloudgroupCount = len(groups) + } + + return rows +} + +func (self *SClouduser) GetIClouduser() (cloudprovider.IClouduser, error) { + account, err := self.GetCloudaccount() + if err != nil { + return nil, errors.Wrap(err, "GetCloudaccount") + } + provider, err := account.GetProvider() + if err != nil { + return nil, errors.Wrap(err, "GetProvider") + } + return provider.GetIClouduserByName(self.Name) +} + +func (manager *SClouduserManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowCreate(userCred, manager) +} + +func (self *SClouduser) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { + return db.IsDomainAllowUpdate(userCred, self) +} + +func (self *SClouduser) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowDelete(userCred, self) +} + +func (manager *SClouduserManager) FetchParentId(ctx context.Context, data jsonutils.JSONObject) string { + input := api.ClouduserCreateInput{} + data.Unmarshal(&input) + if len(input.CloudproviderId) > 0 { + return fmt.Sprintf("cloudprovider_id/%s", input.CloudproviderId) + } + if len(input.CloudaccountId) > 0 { + return fmt.Sprintf("cloudaccount_id/%s", input.CloudaccountId) + } + return "" +} + +func (manager *SClouduserManager) FilterByParentId(q *sqlchemy.SQuery, parentId string) *sqlchemy.SQuery { + if len(parentId) > 0 { + if info := strings.Split(parentId, "/"); len(info) == 2 { + q = q.Equals(info[0], info[1]) + } + } + return q +} + +// 创建公有云用户 +func (manager *SClouduserManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ClouduserCreateInput) (api.ClouduserCreateInput, error) { + var cp *SCloudprovider = nil + var ca *SCloudaccount = nil + if len(input.CloudproviderId) > 0 { + provider, err := CloudproviderManager.FetchProvider(ctx, input.CloudproviderId) + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "CloudproviderManager.FetchProvider")) + } + cp = provider + factory, err := cloudprovider.GetProviderFactory(provider.Provider) + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "GetProviderFactory")) + } + if !factory.IsClouduserBelongCloudprovider() { + return input, httperrors.NewInputParameterError("clouduser not belong cloudprovider for %s", provider.Provider) + } + input.CloudproviderId = provider.Id + input.CloudaccountId = provider.CloudaccountId + } + if len(input.CloudaccountId) == 0 { + return input, httperrors.NewMissingParameterError("cloudaccount_id") + } + account, err := CloudaccountManager.FetchAccount(ctx, input.CloudaccountId) + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "FetchAccount")) + } + if !account.IsSupportCloudId.Bool() { + return input, httperrors.NewUnsupportOperationError("account %s not support create clouduser", account.Name) + } + ca = account + delegate, err := account.getCloudDelegate(ctx) + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "getCloudaccountDelegate")) + } + factory, err := account.GetProviderFactory() + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "GetProviderFactory")) + } + if !factory.IsSupportClouduser() { + return input, httperrors.NewUnsupportOperationError("Not support clouduser for provider %s", delegate.Provider) + } + if factory.IsClouduserBelongCloudprovider() && len(input.CloudproviderId) == 0 { + return input, httperrors.NewMissingParameterError("cloudprovider_id") + } + input.CloudaccountId = delegate.Id + input.StatusUserResourceCreateInput, err = manager.SStatusUserResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StatusUserResourceCreateInput) + if err != nil { + return input, err + } + user, err := db.UserCacheManager.FetchUserById(ctx, input.OwnerId) + if err != nil { + return input, errors.Wrap(err, "FetchUserById") + } + if len(input.Name) == 0 { + input.Name = user.Name + } + + policyExternalIds := []string{} + for _, policyId := range input.CloudpolicyIds { + _policy, err := CloudpolicyManager.FetchById(policyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2("cloudpolicy", policyId) + } + return input, httperrors.NewGeneralError(err) + } + policy := _policy.(*SCloudpolicy) + if policy.Provider != account.Provider { + return input, httperrors.NewConflictError("%s policy can not apply for %s user", policy.Provider, account.Provider) + } + if policy.PolicyType != api.CLOUD_POLICY_TYPE_SYSTEM { + return input, httperrors.NewInputParameterError("current not support custom cloudpolicy") + } + policyExternalIds = append(policyExternalIds, policy.ExternalId) + } + + for _, groupId := range input.CloudgroupIds { + _group, err := CloudgroupManager.FetchById(groupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2("cloudgroup", groupId) + } + return input, httperrors.NewGeneralError(err) + } + group := _group.(*SCloudgroup) + if group.Provider != account.Provider { + return input, httperrors.NewConflictError("%s group can not apply for %s user", group.Provider, account.Provider) + } + policies, err := group.GetCloudpolicies() + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "group.GetCloudpolicies")) + } + if !factory.IsSupportCreateCloudgroup() { + for _, policy := range policies { + if policy.PolicyType == api.CLOUD_POLICY_TYPE_SYSTEM { + policyExternalIds = append(policyExternalIds, policy.ExternalId) + } + } + } + } + + if len(input.CloudpolicyIds) > 0 && !factory.IsSupportClouduserPolicy() { + return input, httperrors.NewInputParameterError("%s not support user policy", account.Provider) + } + + if len(policyExternalIds) == 0 && factory.IsClouduserNeedInitPolicy() { + return input, httperrors.NewMissingParameterError("cloudpolicy_ids or cloudgroup_ids") + } + + var p cloudprovider.ICloudProvider = nil + + if cp != nil { + p, err = cp.GetProvider() + } else if ca != nil { + p, err = ca.GetProvider() + } + + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "cp.GetProvider")) + } + + isConsoleLogin := true + if input.IsConsoleLogin != nil && !*input.IsConsoleLogin { + isConsoleLogin = false + } + input.IsConsoleLogin = &isConsoleLogin + + if isConsoleLogin && len(input.Password) == 0 && factory.IsSupportResetClouduserPassword() { + input.Password = seclib2.RandomPassword2(12) + } + + if len(input.Password) > 0 && !factory.IsSupportResetClouduserPassword() { + return input, httperrors.NewInputParameterError("%s not support password", account.Provider) + } + + if len(input.Password) > 0 { + if !seclib2.MeetComplxity(input.Password) { + return input, httperrors.NewWeakPasswordError() + } + } + + conf := cloudprovider.SClouduserCreateConfig{ + Name: input.Name, + Desc: input.Description, + Password: input.Password, + IsConsoleLogin: isConsoleLogin, + Email: input.Email, + MobilePhone: input.MobilePhone, + ExternalPolicyIds: policyExternalIds, + } + + iUser, err := p.CreateIClouduser(&conf) + if err != nil { + return input, httperrors.NewGeneralError(errors.Wrap(err, "p.CreateIClouduser")) + } + input.ExternalId = iUser.GetGlobalId() + + input.OwnerId = user.Id + return input, nil +} + +func (self *SClouduser) SavePassword(password string) error { + sec, err := utils.EncryptAESBase64(self.Id, password) + if err != nil { + return err + } + _, err = db.Update(self, func() error { + self.Secret = sec + return nil + }) + return err +} + +func (self *SClouduser) GetPassword() (string, error) { + return utils.DescryptAESBase64(self.Id, self.Secret) +} + +func (self *SClouduser) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { + input := api.ClouduserCreateInput{} + data.Unmarshal(&input) + if len(input.Password) > 0 { + self.SavePassword(input.Password) + } + for _, policyId := range input.CloudpolicyIds { + self.attachPolicy(policyId) + } + for _, groupId := range input.CloudgroupIds { + self.joinGroup(groupId) + } + self.StartClouduserSyncTask(ctx, userCred, "") +} + +func (self *SClouduser) SyncWithClouduser(ctx context.Context, userCred mcclient.TokenCredential, iUser cloudprovider.IClouduser, cloudproviderId string) error { + _, err := db.Update(self, func() error { + self.Name = iUser.GetName() + self.Status = api.CLOUD_USER_STATUS_AVAILABLE + switch iUser.IsConsoleLogin() { + case true: + self.IsConsoleLogin = tristate.True + case false: + self.IsConsoleLogin = tristate.False + } + self.CloudproviderId = cloudproviderId + return nil + }) + return err +} + +func (manager *SClouduserManager) newFromClouduser(ctx context.Context, userCred mcclient.TokenCredential, iUser cloudprovider.IClouduser, accountId, providerId string) (*SClouduser, error) { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + user := &SClouduser{} + user.SetModelManager(manager, user) + user.Name = iUser.GetName() + user.ExternalId = iUser.GetGlobalId() + user.Status = api.CLOUD_USER_STATUS_AVAILABLE + user.CloudaccountId = accountId + user.CloudproviderId = providerId + switch iUser.IsConsoleLogin() { + case true: + user.IsConsoleLogin = tristate.True + case false: + user.IsConsoleLogin = tristate.False + } + err := manager.TableSpec().Insert(ctx, user) + if err != nil { + return nil, errors.Wrap(err, "Insert") + } + return user, nil +} + +func (self *SClouduser) detachPolicy(policyId string) error { + policies := []SClouduserPolicy{} + q := ClouduserPolicyManager.Query().Equals("clouduser_id", self.Id).Equals("cloudpolicy_id", policyId) + err := db.FetchModelObjects(ClouduserPolicyManager, q, &policies) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + for i := range policies { + err = policies[i].Delete(context.Background(), nil) + if err != nil { + return errors.Wrap(err, "Delete") + } + } + return nil +} + +func (self *SClouduser) SyncCloudpolicies(ctx context.Context, userCred mcclient.TokenCredential, iPolicies []cloudprovider.ICloudpolicy) compare.SyncResult { + result := compare.SyncResult{} + dbPolicies, err := self.GetCloudpolicies() + if err != nil { + result.Error(errors.Wrap(err, "GetClouduserPolicies")) + return result + } + + removed := make([]SCloudpolicy, 0) + commondb := make([]SCloudpolicy, 0) + commonext := make([]cloudprovider.ICloudpolicy, 0) + added := make([]cloudprovider.ICloudpolicy, 0) + + err = compare.CompareSets(dbPolicies, iPolicies, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return result + } + + result.UpdateCnt = len(commondb) + + for i := 0; i < len(added); i++ { + err = ClouduserPolicyManager.newFromClouduserPolicy(ctx, userCred, added[i], self) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + return result +} + +func (self *SClouduser) GetCloudgroupcaches() ([]SCloudgroupcache, error) { + q := CloudgroupcacheManager.Query().Equals("cloudaccount_id", self.CloudaccountId) + sq := CloudgroupUserManager.Query("cloudgroup_id").Equals("clouduser_id", self.Id).SubQuery() + q = q.In("cloudgroup_id", sq) + caches := []SCloudgroupcache{} + err := db.FetchModelObjects(CloudgroupcacheManager, q, &caches) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return caches, nil +} + +func (self *SClouduser) SyncCloudgroups(ctx context.Context, userCred mcclient.TokenCredential, iGroups []cloudprovider.ICloudgroup) compare.SyncResult { + result := compare.SyncResult{} + dbGroups, err := self.GetCloudgroupcaches() + if err != nil { + result.Error(errors.Wrap(err, "GetCloudgroupcaches")) + return result + } + + removed := make([]SCloudgroupcache, 0) + commondb := make([]SCloudgroupcache, 0) + commonext := make([]cloudprovider.ICloudgroup, 0) + added := make([]cloudprovider.ICloudgroup, 0) + + err = compare.CompareSets(dbGroups, iGroups, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(errors.Wrap(err, "compare.CompareSets")) + return result + } + + result.UpdateCnt = len(commondb) + + for i := 0; i < len(added); i++ { + _cache, err := db.FetchByExternalId(CloudgroupcacheManager, added[i].GetGlobalId()) + if err != nil { + result.AddError(errors.Wrapf(err, "FetchByExternalId(%s)", added[i].GetGlobalId())) + continue + } + cache := _cache.(*SCloudgroupcache) + err = self.joinGroup(cache.CloudgroupId) + if err != nil { + result.AddError(errors.Wrap(err, "joinGroup")) + continue + } + result.Add() + } + + return result +} + +// 删除公有云用户 +func (self *SClouduser) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + params := jsonutils.NewDict() + return self.StartClouduserDeleteTask(ctx, userCred, params, "") +} + +func (self *SClouduser) StartClouduserDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "ClouduserDeleteTask", self, userCred, data, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_USER_STATUS_DELETING, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SClouduser) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return nil +} + +func (self *SClouduser) GetClouduserPolicies() ([]SClouduserPolicy, error) { + policies := []SClouduserPolicy{} + q := ClouduserPolicyManager.Query().Equals("clouduser_id", self.Id) + err := db.FetchModelObjects(ClouduserPolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return policies, nil +} + +func (self *SClouduser) GetCloudpolicyQuery() *sqlchemy.SQuery { + q := CloudpolicyManager.Query() + sq := ClouduserPolicyManager.Query().SubQuery() + return q.Join(sq, sqlchemy.Equals(q.Field("id"), sq.Field("cloudpolicy_id"))).Filter(sqlchemy.Equals(sq.Field("clouduser_id"), self.Id)) +} + +func (self *SClouduser) GetCloudpolicyCount() (int, error) { + return self.GetCloudpolicyQuery().CountWithError() +} + +func (self *SClouduser) GetCloudpolicies() ([]SCloudpolicy, error) { + policies := []SCloudpolicy{} + q := self.GetCloudpolicyQuery() + err := db.FetchModelObjects(CloudpolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return policies, nil +} + +func (self *SClouduser) joinGroup(groupId string) error { + gu := &SCloudgroupUser{} + gu.SetModelManager(CloudgroupUserManager, gu) + gu.ClouduserId = self.Id + gu.CloudgroupId = groupId + return CloudgroupUserManager.TableSpec().Insert(context.Background(), gu) +} + +func (self *SClouduser) leaveGroup(groupId string) error { + ugs := []SCloudgroupUser{} + q := CloudgroupUserManager.Query().Equals("clouduser_id", self.Id).Equals("cloudgroup_id", groupId) + err := db.FetchModelObjects(CloudgroupUserManager, q, &ugs) + if err != nil { + return errors.Wrap(err, "FetchModelObjects") + } + for i := range ugs { + err = ugs[i].Delete(context.Background(), nil) + if err != nil { + return errors.Wrapf(err, "ug %d Delete", ugs[i].RowId) + } + } + return nil +} + +func (self *SClouduser) leaveGroups() error { + q := CloudgroupUserManager.Query().Equals("clouduser_id", self.Id) + groups := []SCloudgroupUser{} + err := db.FetchModelObjects(CloudgroupUserManager, q, &groups) + if err != nil { + return errors.Wrap(err, "db.FetchModelObjects") + } + for i := range groups { + err = groups[i].Delete(context.Background(), nil) + if err != nil { + return errors.Wrap(err, "group user delete") + } + } + return nil +} + +func (self *SClouduser) removePolicies() error { + policies, err := self.GetCloudpolicies() + if err != nil { + return errors.Wrap(err, "GetCloudpolicies") + } + for i := range policies { + err = self.detachPolicy(policies[i].Id) + if err != nil { + return errors.Wrapf(err, "detachPolicy(%s)", policies[i].Id) + } + } + return nil +} + +func (self *SClouduser) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := self.removePolicies() + if err != nil { + return errors.Wrap(err, "removePolicies") + } + err = self.leaveGroups() + if err != nil { + return errors.Wrap(err, "leaveGroups") + } + return self.SStatusUserResourceBase.Delete(ctx, userCred) +} + +func (self *SClouduser) GetCloudpolicy(policyId string) (*SCloudpolicy, error) { + policies := []SCloudpolicy{} + q := self.GetCloudpolicyQuery().Equals("id", policyId) + err := db.FetchModelObjects(CloudpolicyManager, q, &policies) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + if len(policies) > 1 { + return nil, sqlchemy.ErrDuplicateEntry + } + if len(policies) == 0 { + return nil, sql.ErrNoRows + } + return &policies[0], nil +} + +func (self *SClouduser) AllowPerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "set-policies") +} + +// 设置用户权限列表(全量覆盖) +// 用户状态必须为: available +func (self *SClouduser) PerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserSetPoliciesInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not set policies in status %s", self.Status) + } + + account, err := self.GetCloudaccount() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + factory, err := account.GetProviderFactory() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if !factory.IsSupportClouduserPolicy() { + return nil, httperrors.NewUnsupportOperationError("Unsupport operation for %s user", account.Provider) + } + + policies, err := self.GetCloudpolicies() + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + policyMaps := map[string]*SCloudpolicy{} + local := set.New(set.ThreadSafe) + for i := range policies { + local.Add(policies[i].Id) + policyMaps[policies[i].Id] = &policies[i] + } + + newP := set.New(set.ThreadSafe) + + for _, policyId := range input.CloudpolicyIds { + _policy, err := CloudpolicyManager.FetchById(policyId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", policyId) + } + return nil, httperrors.NewGeneralError(err) + } + policy := _policy.(*SCloudpolicy) + if policy.Provider != account.Provider { + return nil, httperrors.NewConflictError("policy %s(%s) and group not with same provider", policy.Name, policy.Id) + } + newP.Add(policyId) + } + + for _, del := range set.Difference(local, newP).List() { + id := del.(string) + policy, ok := policyMaps[id] + if ok { + err = self.detachPolicy(policy.Id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "detachPolicy")) + } + logclient.AddSimpleActionLog(self, logclient.ACT_DETACH_POLICY, policy, userCred, true) + } + } + + for _, add := range set.Difference(newP, local).List() { + id := add.(string) + policy, ok := policyMaps[id] + if ok { + err = self.attachPolicy(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "AttachPolicy")) + } + logclient.AddSimpleActionLog(self, logclient.ACT_ATTACH_POLICY, policy, userCred, true) + } + } + + return nil, self.StartClouduserSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SClouduser) AllowPerformSetGroups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "set-groups") +} + +// 设置用户权限组列表(全量覆盖) +// 用户状态必须为: available +func (self *SClouduser) PerformSetGroups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserSetGroupsInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not set groups in status %s", self.Status) + } + + account, err := self.GetCloudaccount() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetCloudaccount")) + } + + groups, err := self.GetCloudgroups() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetCloudgroups")) + } + + local := set.New(set.ThreadSafe) + groupMaps := map[string]*SCloudgroup{} + for i := range groups { + local.Add(groups[i].Id) + groupMaps[groups[i].Id] = &groups[i] + } + + newG := set.New(set.ThreadSafe) + for _, groupId := range input.CloudgroupIds { + _group, err := CloudgroupManager.FetchById(groupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", groupId) + } + return nil, httperrors.NewGeneralError(err) + } + group := _group.(*SCloudgroup) + if group.Provider != account.Provider { + return nil, httperrors.NewConflictError("group %s(%s) and user not with same provider", group.Name, group.Id) + } + newG.Add(groupId) + groupMaps[group.Id] = group + } + for _, del := range set.Difference(local, newG).List() { + id := del.(string) + group, ok := groupMaps[id] + if ok { + err = self.leaveGroup(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "leaveGroup(%s)", group.Name)) + } + logclient.AddSimpleActionLog(self, logclient.ACT_REMOVE_USER, group, userCred, true) + logclient.AddSimpleActionLog(group, logclient.ACT_REMOVE_USER, self, userCred, true) + } + } + + for _, add := range set.Difference(newG, local).List() { + id := add.(string) + group, ok := groupMaps[id] + if ok { + err = self.joinGroup(id) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "%s.AddUser", group.Name)) + } + logclient.AddSimpleActionLog(self, logclient.ACT_ADD_USER, group, userCred, true) + logclient.AddSimpleActionLog(group, logclient.ACT_ADD_USER, self, userCred, true) + } + } + + return nil, self.StartClouduserSyncGroupsTask(ctx, userCred, "") +} + +func (self *SClouduser) AllowPerformJoinGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "join-group") +} + +// 将用户加入权限组 +// 用户状态必须为: available +func (self *SClouduser) PerformJoinGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserJoinGroupInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not join group in status %s", self.Status) + } + if len(input.CloudgroupId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudgroup_id") + } + group, err := CloudgroupManager.FetchById(input.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", input.CloudgroupId) + } + return nil, httperrors.NewGeneralError(err) + } + + err = self.joinGroup(group.GetId()) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + logclient.AddSimpleActionLog(group, logclient.ACT_ADD_USER, self, userCred, true) + logclient.AddSimpleActionLog(self, logclient.ACT_ADD_USER, group, userCred, true) + return nil, self.StartClouduserSyncGroupsTask(ctx, userCred, "") +} + +func (self *SClouduser) AllowPerformLeaveGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "leave-group") +} + +// 将用户从权限组中移除 +// 用户状态必须为: available +func (self *SClouduser) PerformLeaveGroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserLeaveGroupInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not leave group in status %s", self.Status) + } + if len(input.CloudgroupId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudgroup_id") + } + group, err := CloudgroupManager.FetchById(input.CloudgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudgroup", input.CloudgroupId) + } + return nil, httperrors.NewGeneralError(err) + } + + err = self.leaveGroup(group.GetId()) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + logclient.AddSimpleActionLog(group, logclient.ACT_REMOVE_USER, self, userCred, true) + logclient.AddSimpleActionLog(self, logclient.ACT_REMOVE_USER, group, userCred, true) + return nil, self.StartClouduserSyncGroupsTask(ctx, userCred, "") +} + +func (self *SClouduser) AllowPerformAttachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "attach-policy") +} + +// 绑定用户权限 +// 用户状态必须为: available +func (self *SClouduser) PerformAttachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserAttachPolicyInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not detach policy in status %s", self.Status) + } + if len(input.CloudpolicyId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudpolicy_id") + } + + account, err := self.GetCloudaccount() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetCloudaccount")) + } + + factory, err := account.GetProviderFactory() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetProviderFactory")) + } + + if !factory.IsSupportClouduserPolicy() { + return nil, httperrors.NewNotSupportedError("%s not support attach policy for user", account.Provider) + } + + _policy, err := CloudpolicyManager.FetchById(input.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", input.CloudpolicyId) + } + return nil, httperrors.NewGeneralError(err) + } + + policy := _policy.(*SCloudpolicy) + if policy.Provider != account.Provider { + return nil, httperrors.NewDuplicateResourceError("user and policy not with same provider") + } + _, err = self.GetCloudpolicy(input.CloudpolicyId) + if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry { + return nil, httperrors.NewDuplicateResourceError("policy %s has aleady binding this user", input.CloudpolicyId) + } + + err = self.attachPolicy(input.CloudpolicyId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "AttachPolicy")) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_ATTACH_POLICY, policy, userCred, true) + return nil, self.StartClouduserSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SClouduser) attachPolicy(policyId string) error { + up := &SClouduserPolicy{} + up.SetModelManager(ClouduserPolicyManager, up) + up.CloudpolicyId = policyId + up.ClouduserId = self.Id + return ClouduserPolicyManager.TableSpec().Insert(context.Background(), up) +} + +func (self *SClouduser) AllowPerformDetachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "detach-policy") +} + +// 解绑用户权限 +// 用户状态必须为: available +func (self *SClouduser) PerformDetachPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserDetachPolicyInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not detach policy in status %s", self.Status) + } + if len(input.CloudpolicyId) == 0 { + return nil, httperrors.NewMissingParameterError("cloudpolicy_id") + } + account, err := self.GetCloudaccount() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetCloudaccount")) + } + + factory, err := account.GetProviderFactory() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetProviderFactory")) + } + + if !factory.IsSupportClouduserPolicy() { + return nil, httperrors.NewUnsupportOperationError("Unsupport operation for %s user", account.Provider) + } + + policyCount, _ := self.GetCloudpolicyCount() + if minPolicyCount := factory.GetClouduserMinPolicyCount(); minPolicyCount > 0 && minPolicyCount >= policyCount { + return nil, httperrors.NewUnsupportOperationError("%s that at least %d cloudpolicy be retained", account.Provider, minPolicyCount) + } + + policy, err := CloudpolicyManager.FetchById(input.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("cloudpolicy", input.CloudpolicyId) + } + return nil, httperrors.NewGeneralError(err) + } + + _, err = self.GetCloudpolicy(input.CloudpolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, nil + } + return nil, httperrors.NewGeneralError(err) + } + + err = self.detachPolicy(input.CloudpolicyId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "detachPolicy")) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_DETACH_POLICY, policy, userCred, true) + return nil, self.StartClouduserSyncPoliciesTask(ctx, userCred, "") +} + +func (self *SClouduser) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "syncstatus") +} + +// 同步用户状态 +func (self *SClouduser) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserSyncstatusInput) (jsonutils.JSONObject, error) { + if len(self.ExternalId) == 0 { + return nil, httperrors.NewGeneralError(fmt.Errorf("not managed resource")) + } + return nil, self.StartClouduserSyncstatusTask(ctx, userCred, "") +} + +func (self *SClouduser) StartClouduserSyncstatusTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "ClouduserSyncstatusTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_USER_STATUS_SYNC_STATUS, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SClouduser) AllowPerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "sync") +} + +// 同步用户权限和权限组到云上 +// 用户状态必须为: available +func (self *SClouduser) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserSyncInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not sync in status %s", self.Status) + } + return nil, self.StartClouduserSyncTask(ctx, userCred, "") +} + +func (self *SClouduser) StartClouduserSyncTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "ClouduserSyncTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_USER_STATUS_SYNC, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SClouduser) StartClouduserSyncPoliciesTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "ClouduserSyncPoliciesTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_USER_STATUS_SYNC_POLICIES, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SClouduser) StartClouduserSyncGroupsTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + task, err := taskman.TaskManager.NewTask(ctx, "ClouduserSyncGroupsTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_USER_STATUS_SYNC_GROUPS, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SClouduser) AllowPerformResetPassword(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return db.IsDomainAllowPerform(userCred, self, "reset-password") +} + +// 重置用户密码 +// 用户状态必须为: available +func (self *SClouduser) PerformResetPassword(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserResetPasswordInput) (jsonutils.JSONObject, error) { + if self.Status != api.CLOUD_USER_STATUS_AVAILABLE { + return nil, httperrors.NewInvalidStatusError("Can not reset password in status %s", self.Status) + } + + account, err := self.GetCloudaccount() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetCloudaccount")) + } + factory, err := account.GetProviderFactory() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetProviderFactory")) + } + + if !factory.IsSupportResetClouduserPassword() { + return nil, httperrors.NewUnsupportOperationError("Not support reset %s user password", account.Provider) + } + + return nil, self.StartClouduserResetPasswordTask(ctx, userCred, input.Password, "") +} + +func (self *SClouduser) StartClouduserResetPasswordTask(ctx context.Context, userCred mcclient.TokenCredential, password string, parentTaskId string) error { + if len(password) == 0 { + password = seclib2.RandomPassword2(12) + } + params := jsonutils.Marshal(map[string]string{"password": password}).(*jsonutils.JSONDict) + task, err := taskman.TaskManager.NewTask(ctx, "ClouduserResetPasswordTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return errors.Wrap(err, "NewTask") + } + self.SetStatus(userCred, api.CLOUD_USER_STATUS_RESET_PASSWORD, "") + task.ScheduleRun(nil) + return nil +} + +func (self *SClouduser) AllowPerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserChangeOwnerInput) bool { + return db.IsDomainAllowPerform(userCred, self, "change-owner") +} + +// 变更子账号所属本地用户 +func (self *SClouduser) PerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ClouduserChangeOwnerInput) (jsonutils.JSONObject, error) { + user, err := db.UserCacheManager.FetchById(input.UserId) + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "Not found user %s", input.UserId)) + } + old := self.OwnerId + _, err = db.Update(self, func() error { + self.OwnerId = user.GetId() + return nil + }) + + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + logclient.AddSimpleActionLog(self, logclient.ACT_CHANGE_OWNER, map[string]interface{}{"old": old, "new": user}, userCred, true) + return nil, self.StartClouduserResetPasswordTask(ctx, userCred, "", "") +} + +func (self *SClouduser) GetCloudgroupQuery() *sqlchemy.SQuery { + sq := CloudgroupUserManager.Query("cloudgroup_id").Equals("clouduser_id", self.Id) + return CloudgroupManager.Query().In("id", sq.SubQuery()) +} + +func (self *SClouduser) GetCloudgroupCount() (int, error) { + return self.GetCloudgroupQuery().CountWithError() +} + +func (self *SClouduser) GetCloudgroups() ([]SCloudgroup, error) { + q := self.GetCloudgroupQuery() + groups := []SCloudgroup{} + err := db.FetchModelObjects(CloudgroupManager, q, &groups) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return groups, nil +} + +// 将本地的权限推送到云上(覆盖云上设置) +func (self *SClouduser) SyncCloudpoliciesForCloud(ctx context.Context) (result compare.SyncResult, err error) { + lockman.LockObject(ctx, self) + defer lockman.ReleaseObject(ctx, self) + + iUser, err := self.GetIClouduser() + if err != nil { + return result, errors.Wrap(err, "GetIClouduser") + } + iPolicies, err := iUser.GetISystemCloudpolicies() + if err != nil { + return result, errors.Wrap(err, "GetISystemCloudpolicies") + } + + dbPolicies, err := self.GetCloudpolicies() + if err != nil { + return result, errors.Wrap(err, "GetCloudpolicies") + } + + added := make([]SCloudpolicy, 0) + commondb := make([]SCloudpolicy, 0) + commonext := make([]cloudprovider.ICloudpolicy, 0) + removed := make([]cloudprovider.ICloudpolicy, 0) + + err = compare.CompareSets(dbPolicies, iPolicies, &added, &commondb, &commonext, &removed) + if err != nil { + return result, errors.Wrap(err, "compare.CompareSets") + } + + for i := 0; i < len(removed); i++ { + err = iUser.DetachSystemPolicy(removed[i].GetGlobalId()) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + result.UpdateCnt = len(commondb) + + for i := 0; i < len(added); i++ { + err = iUser.AttachSystemPolicy(added[i].ExternalId) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + + return result, nil +} diff --git a/pkg/cloudid/models/clouduser_policies.go b/pkg/cloudid/models/clouduser_policies.go new file mode 100644 index 0000000000..7d95b017f7 --- /dev/null +++ b/pkg/cloudid/models/clouduser_policies.go @@ -0,0 +1,207 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +// +onecloud:swagger-gen-ignore +type SClouduserPolicyManager struct { + SClouduserJointsManager + + SCloudpolicyResourceBaseManager +} + +var ClouduserPolicyManager *SClouduserPolicyManager + +func init() { + db.InitManager(func() { + ClouduserPolicyManager = &SClouduserPolicyManager{ + SClouduserJointsManager: NewClouduserJointsManager( + SClouduserPolicy{}, + "clouduser_policies_tbl", + "clouduser_policy", + "clouduser_policies", + CloudpolicyManager, + ), + } + ClouduserPolicyManager.SetVirtualObject(ClouduserPolicyManager) + ClouduserPolicyManager.TableSpec().AddIndex(true, "clouduser_id", "cloudpolicy_id") + }) + +} + +type SClouduserPolicy struct { + SClouduserJointsBase + + SCloudpolicyResourceBase +} + +func (manager *SClouduserPolicyManager) GetSlaveFieldName() string { + return "cloudpolicy_id" +} + +func (manager *SClouduserPolicyManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +func (self *SClouduserPolicy) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return false +} + +// +onecloud:swagger-gen-ignore +func (manager *SClouduserPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewNotSupportedError("Not Supported") +} + +// +onecloud:swagger-gen-ignore +func (self *SClouduserPolicy) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.SClouduserJointsBase.CustomizeDelete(ctx, userCred, query, data) +} + +// +onecloud:swagger-gen-ignore +func (self *SClouduserPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, httperrors.NewNotSupportedError("Not Supported") +} + +func (joint *SClouduserPolicy) Master() db.IStandaloneModel { + return db.JointMaster(joint) +} + +func (joint *SClouduserPolicy) Slave() db.IStandaloneModel { + return db.JointSlave(joint) +} + +// 获取公有云用户权限详情 +func (self *SClouduserPolicy) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.ClouduserPolicyDetails, error) { + return api.ClouduserPolicyDetails{}, nil +} + +func (manager *SClouduserPolicyManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ClouduserPolicyDetails { + rows := make([]api.ClouduserPolicyDetails, len(objs)) + + userRows := manager.SClouduserJointsManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + policyRows := manager.SCloudpolicyResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.ClouduserPolicyDetails{ + ClouduserJointResourceDetails: userRows[i], + CloudpolicyResourceDetails: policyRows[i], + } + } + + return rows +} + +func (self *SClouduserPolicy) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +func (self *SClouduserPolicy) Detach(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DetachJoint(ctx, userCred, self) +} + +// 公有云用户权限列表 +func (manager *SClouduserPolicyManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ClouduserPolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SClouduserJointsManager.ListItemFilter(ctx, q, userCred, query.ClouduserJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SClouduserJointsManager.ListItemFilter") + } + q, err = manager.SCloudpolicyResourceBaseManager.ListItemFilter(ctx, q, userCred, query.CloudpolicyResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +func (manager *SClouduserPolicyManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ClouduserPolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SClouduserJointsManager.OrderByExtraFields(ctx, q, userCred, query.ClouduserJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SClouduserJointsManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SClouduserPolicyManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SClouduserJointsManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SClouduserJointsManager.ListItemExportKeys") + } + + return q, nil +} + +func (manager *SClouduserPolicyManager) newFromClouduserPolicy(ctx context.Context, userCred mcclient.TokenCredential, iPolicy cloudprovider.ICloudpolicy, user *SClouduser) error { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + up := &SClouduserPolicy{} + up.SetModelManager(manager, up) + up.ClouduserId = user.Id + + p, err := db.FetchByExternalId(CloudpolicyManager, iPolicy.GetGlobalId()) + if err != nil { + return errors.Wrapf(err, "db.FetchByExternalId(%s)", iPolicy.GetGlobalId()) + } + up.CloudpolicyId = p.GetId() + + return manager.TableSpec().Insert(ctx, up) +} diff --git a/pkg/cloudid/models/clouduserjoints.go b/pkg/cloudid/models/clouduserjoints.go new file mode 100644 index 0000000000..f66a2328cd --- /dev/null +++ b/pkg/cloudid/models/clouduserjoints.go @@ -0,0 +1,149 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SClouduserJointsManager struct { + db.SJointResourceBaseManager + SClouduserResourceBaseManager +} + +func NewClouduserJointsManager(dt interface{}, tableName string, keyword string, keywordPlural string, slave db.IVirtualModelManager) SClouduserJointsManager { + return SClouduserJointsManager{ + SJointResourceBaseManager: db.NewJointResourceBaseManager( + dt, + tableName, + keyword, + keywordPlural, + ClouduserManager, + slave, + ), + } +} + +type SClouduserJointsBase struct { + db.SJointResourceBase + + SClouduserResourceBase +} + +func (manager *SClouduserJointsManager) GetMasterFieldName() string { + return "clouduser_id" +} + +func (self *SClouduserJointsBase) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.ClouduserJointResourceDetails, error) { + return api.ClouduserJointResourceDetails{}, nil +} + +func (manager *SClouduserJointsManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ClouduserJointResourceDetails { + rows := make([]api.ClouduserJointResourceDetails, len(objs)) + + jointRows := manager.SJointResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + userRows := manager.SClouduserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.ClouduserJointResourceDetails{ + JointResourceBaseDetails: jointRows[i], + ClouduserResourceDetails: userRows[i], + } + } + return rows +} + +func (manager *SClouduserJointsManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ClouduserJointsListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SJointResourceBaseManager.ListItemFilter(ctx, q, userCred, query.JointResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SJointResourceBaseManager.ListItemFilter") + } + q, err = manager.SClouduserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ClouduserResourceListInput) + if err != nil { + return nil, err + } + + return q, nil +} + +func (manager *SClouduserJointsManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ClouduserJointsListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SJointResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.JointResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SJointResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (self *SClouduserJointsBase) ValidateUpdateData( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + input api.ClouduserJointBaseUpdateInput, +) (api.ClouduserJointBaseUpdateInput, error) { + var err error + input.JointResourceBaseUpdateInput, err = self.SJointResourceBase.ValidateUpdateData(ctx, userCred, query, input.JointResourceBaseUpdateInput) + if err != nil { + return input, errors.Wrap(err, "SJointResourceBase.ValidateUpdateData") + } + return input, nil +} + +func (manager *SClouduserJointsManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SJointResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SJointResourceBaseManager.ListItemExportKeys") + } + return q, nil +} diff --git a/pkg/cloudid/models/clouduserresource.go b/pkg/cloudid/models/clouduserresource.go new file mode 100644 index 0000000000..8d246bb5eb --- /dev/null +++ b/pkg/cloudid/models/clouduserresource.go @@ -0,0 +1,115 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + "database/sql" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SClouduserResourceBaseManager struct { +} + +type SClouduserResourceBase struct { + ClouduserId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` +} + +func (self *SClouduserJointsBase) GetClouduser() (*SClouduser, error) { + user, err := ClouduserManager.FetchById(self.ClouduserId) + if err != nil { + return nil, errors.Wrap(err, "FetchById") + } + return user.(*SClouduser), nil +} + +func (manager *SClouduserResourceBaseManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ClouduserResourceListInput) (*sqlchemy.SQuery, error) { + if len(query.Clouduser) > 0 { + user, err := ClouduserManager.FetchByIdOrName(nil, query.Clouduser) + if err != nil { + if err == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("clouduser", query.Clouduser) + } + return nil, httperrors.NewGeneralError(err) + } + q = q.Equals("clouduser_id", user.GetId()) + } + return q, nil +} + +func (manager *SClouduserResourceBaseManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ClouduserResourceDetails { + rows := make([]api.ClouduserResourceDetails, len(objs)) + userIds := make([]string, len(objs)) + for i := range objs { + var base *SClouduserResourceBase + err := reflectutils.FindAnonymouStructPointer(objs[i], &base) + if err != nil { + log.Errorf("Cannot find SClouduserResourceBase in %#v: %s", objs[i], err) + } else if base != nil && len(base.ClouduserId) > 0 { + userIds[i] = base.ClouduserId + } + } + + users := make(map[string]SClouduser) + err := db.FetchStandaloneObjectsByIds(ClouduserManager, userIds, &users) + if err != nil { + log.Errorf("FetchStandaloneObjectsByIds fail %v", err) + return rows + } + accountIds := make([]string, len(objs)) + providerIds := make([]string, len(objs)) + for i := range rows { + if user, ok := users[userIds[i]]; ok { + rows[i].Clouduser = user.Name + accountIds[i] = user.CloudaccountId + providerIds[i] = user.CloudproviderId + } + } + accountMaps, err := db.FetchIdNameMap2(CloudaccountManager, accountIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail %v", err) + return rows + } + + providerMaps, err := db.FetchIdNameMap2(CloudproviderManager, providerIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail %v", err) + return rows + } + + for i := range rows { + rows[i].Cloudaccount, _ = accountMaps[accountIds[i]] + rows[i].Cloudprovider, _ = providerMaps[providerIds[i]] + } + return rows +} diff --git a/pkg/cloudid/models/doc.go b/pkg/cloudid/models/doc.go new file mode 100644 index 0000000000..6e412d960b --- /dev/null +++ b/pkg/cloudid/models/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models // import "yunion.io/x/onecloud/pkg/cloudid/models" diff --git a/pkg/cloudid/models/initdb.go b/pkg/cloudid/models/initdb.go new file mode 100644 index 0000000000..173a348fb5 --- /dev/null +++ b/pkg/cloudid/models/initdb.go @@ -0,0 +1,19 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +func InitDB() error { + return nil +} diff --git a/pkg/cloudid/options/doc.go b/pkg/cloudid/options/doc.go new file mode 100644 index 0000000000..17ff666c96 --- /dev/null +++ b/pkg/cloudid/options/doc.go @@ -0,0 +1 @@ +package options // import "yunion.io/x/onecloud/pkg/cloudid/options" diff --git a/pkg/cloudid/options/options.go b/pkg/cloudid/options/options.go new file mode 100644 index 0000000000..2a1d85c0c3 --- /dev/null +++ b/pkg/cloudid/options/options.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package options + +import ( + common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" +) + +type SCloudIdOptions struct { + common_options.CommonOptions + common_options.DBOptions + + CloudaccountSyncIntervalMinutes int `help:"frequency to sync region cloudaccount task" default:"3"` + CloudpolicySyncIntervalHours int `help:"frequency to sync region cloudpolicy task" default:"12"` + CloudgroupSyncIntervalHours int `help:"frequency to sync region cloudgrouptask" default:"3"` + ClouduserSyncIntervalHours int `help:"frequency to sync clouduser task" default:"7"` +} + +var ( + Options SCloudIdOptions +) diff --git a/pkg/cloudid/service/doc.go b/pkg/cloudid/service/doc.go new file mode 100644 index 0000000000..de28f13e34 --- /dev/null +++ b/pkg/cloudid/service/doc.go @@ -0,0 +1 @@ +package service // import "yunion.io/x/onecloud/pkg/cloudid/service" diff --git a/pkg/cloudid/service/handlers.go b/pkg/cloudid/service/handlers.go new file mode 100644 index 0000000000..701e2f6bc6 --- /dev/null +++ b/pkg/cloudid/service/handlers.go @@ -0,0 +1,69 @@ +package service + +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import ( + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/appsrv/dispatcher" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/proxy" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +func InitHandlers(app *appsrv.Application) { + db.InitAllManagers() + + taskman.AddTaskHandler("v1", app) + db.AddScopeResourceCountHandler("", app) + + for _, manager := range []db.IModelManager{ + taskman.TaskManager, + taskman.SubTaskManager, + taskman.TaskObjectManager, + db.UserCacheManager, + db.TenantCacheManager, + db.SharedResourceManager, + db.Metadata, + models.CloudaccountManager, + models.CloudproviderManager, + } { + db.RegisterModelManager(manager) + } + + for _, manager := range []db.IModelManager{ + db.OpsLog, + proxy.ProxySettingManager, + models.ClouduserManager, + models.CloudgroupManager, + models.CloudgroupcacheManager, + models.CloudpolicyManager, + } { + db.RegisterModelManager(manager) + handler := db.NewModelHandler(manager) + dispatcher.AddModelDispatcher("", app, handler) + } + + for _, manager := range []db.IJointModelManager{ + models.ClouduserPolicyManager, + models.CloudgroupPolicyManager, + models.CloudgroupUserManager, + } { + db.RegisterModelManager(manager) + handler := db.NewJointModelHandler(manager) + dispatcher.AddJointModelDispatcher("", app, handler) + } + +} diff --git a/pkg/cloudid/service/service.go b/pkg/cloudid/service/service.go new file mode 100644 index 0000000000..a3f53a0ec7 --- /dev/null +++ b/pkg/cloudid/service/service.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "os" + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudcommon" + common_app "yunion.io/x/onecloud/pkg/cloudcommon/app" + "yunion.io/x/onecloud/pkg/cloudcommon/cronman" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + common_options "yunion.io/x/onecloud/pkg/cloudcommon/options" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudid/options" + _ "yunion.io/x/onecloud/pkg/cloudid/tasks" + _ "yunion.io/x/onecloud/pkg/multicloud/loader" +) + +func StartService() { + opts := &options.Options + dbOpts := &opts.DBOptions + baseOpts := &opts.BaseOptions + commonOpts := &opts.CommonOptions + common_options.ParseOptions(opts, os.Args, "cloudid.conf", "cloudid") + + common_app.InitAuth(commonOpts, func() { + log.Infof("Auth complete!!") + }) + + app := common_app.InitApp(baseOpts, false) + InitHandlers(app) + + db.EnsureAppInitSyncDB(app, dbOpts, models.InitDB) + defer cloudcommon.CloseDB() + + if !opts.IsSlaveNode { + cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount) + cron.AddJobAtIntervalsWithStartRun("SyncCloudaccounts", time.Duration(opts.CloudaccountSyncIntervalMinutes)*time.Minute, models.CloudaccountManager.SyncCloudaccounts, true) + cron.AddJobAtIntervalsWithStartRun("SyncCloudpolicies", time.Duration(opts.CloudpolicySyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudpolicies, true) + cron.AddJobAtIntervalsWithStartRun("SyncCloudgroups", time.Duration(opts.CloudgroupSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudgroups, true) + cron.AddJobAtIntervalsWithStartRun("SyncCloudusersTask", time.Duration(opts.ClouduserSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudusers, true) + cron.Start() + defer cron.Stop() + } + + common_app.ServeForever(app, baseOpts) +} diff --git a/pkg/cloudid/tasks/cloudgroup_delete_task.go b/pkg/cloudid/tasks/cloudgroup_delete_task.go new file mode 100644 index 0000000000..a7bd67a05f --- /dev/null +++ b/pkg/cloudid/tasks/cloudgroup_delete_task.go @@ -0,0 +1,82 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type CloudgroupDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(CloudgroupDeleteTask{}) +} + +func (self *CloudgroupDeleteTask) taskFailed(ctx context.Context, group *models.SCloudgroup, err error) { + group.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, group, logclient.ACT_DELETE, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *CloudgroupDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + group := obj.(*models.SCloudgroup) + + caches, err := group.GetCloudgroupcaches() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetCloudgroupcaches")) + return + } + + for i := range caches { + iGroup, err := caches[i].GetICloudgroup() + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + self.taskFailed(ctx, group, errors.Wrap(err, "caches[i].GetICloudgroup")) + return + } + if err == nil { + err = iGroup.Delete() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "iGroup.Delete")) + return + } + } + caches[i].RealDelete(ctx, self.GetUserCred()) + } + + cnt, err := group.GetCloudgroupcacheCount() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetCloudgroupcacheCount")) + return + } + if cnt == 0 { + group.RealDelete(ctx, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, group, logclient.ACT_DELETE, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) + return + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/cloudgroup_sync_policies_task.go b/pkg/cloudid/tasks/cloudgroup_sync_policies_task.go new file mode 100644 index 0000000000..0e7c9d25dc --- /dev/null +++ b/pkg/cloudid/tasks/cloudgroup_sync_policies_task.go @@ -0,0 +1,99 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type CloudgroupSyncPoliciesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(CloudgroupSyncPoliciesTask{}) +} + +func (self *CloudgroupSyncPoliciesTask) taskFailed(ctx context.Context, group *models.SCloudgroup, err error) { + group.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_STATUS_SYNC_POLICIES, err.Error()) + logclient.AddActionLogWithStartable(self, group, logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *CloudgroupSyncPoliciesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + group := obj.(*models.SCloudgroup) + + factory, err := group.GetProviderFactory() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "group.GetProviderFactory")) + return + } + + if !factory.IsSupportCreateCloudgroup() { + if factory.IsSupportClouduserPolicy() { + users, err := group.GetCloudusers() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "group.GetCloudusers")) + return + } + for i := range users { + result, err := users[i].SyncCloudpoliciesForCloud(ctx) + if err != nil { + logclient.AddActionLogWithStartable(self, &users[i], logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + continue + } + log.Infof("Sync cloudpolicies for user %s(%s) result: %s", users[i].Name, users[i].Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, group, result.AllError()) + return + } + } + } + } else { + caches, err := group.GetCloudgroupcaches() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetCloudgroupcaches")) + return + } + + for i := range caches { + result, err := caches[i].SyncCloudpoliciesForCloud(ctx) + if err != nil { + logclient.AddActionLogWithStartable(self, &caches[i], logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + continue + } + log.Infof("Sync cloudpolicies for group cache %s(%s) result: %s", caches[i].Name, caches[i].Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, group, result.AllError()) + return + } + } + } + + group.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/cloudgroup_sync_users_task.go b/pkg/cloudid/tasks/cloudgroup_sync_users_task.go new file mode 100644 index 0000000000..d108a116e2 --- /dev/null +++ b/pkg/cloudid/tasks/cloudgroup_sync_users_task.go @@ -0,0 +1,139 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type CloudgroupSyncUsersTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(CloudgroupSyncUsersTask{}) +} + +func (self *CloudgroupSyncUsersTask) taskFailed(ctx context.Context, group *models.SCloudgroup, err error) { + group.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_STATUS_SYNC_POLICIES, err.Error()) + logclient.AddActionLogWithStartable(self, group, logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *CloudgroupSyncUsersTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + group := obj.(*models.SCloudgroup) + + factory, err := group.GetProviderFactory() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetProviderFactory")) + return + } + + users, err := group.GetCloudusers() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetCloudusers")) + return + } + + if !factory.IsSupportCreateCloudgroup() && factory.IsSupportClouduserPolicy() { + for i := range users { + result, err := users[i].SyncCloudpoliciesForCloud(ctx) + if err != nil { + logclient.AddActionLogWithStartable(self, &users[i], logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + continue + } + log.Infof("Sync cloudpolicies for user %s(%s) result: %s", users[i].Name, users[i].Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, group, result.AllError()) + return + } + } + } else { + caches, err := group.GetCloudgroupcaches() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetCloudgroupcaches")) + return + } + + accounts := map[string]string{} + + for i := range caches { + account, err := caches[i].GetCloudaccount() + if err != nil { + log.Errorf("failed to get cloudaccoutn for cache %s(%s) error: %v", caches[i].Name, caches[i].Id, err) + continue + } + accounts[account.Id] = account.Name + result, err := caches[i].SyncCloudusersForCloud(ctx) + if err != nil { + logclient.AddActionLogWithStartable(self, &caches[i], logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + continue + } + log.Infof("Sync cloudpolicies for group cache %s(%s) result: %s", caches[i].Name, caches[i].Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, group, result.AllError()) + return + } + } + + for _, user := range users { + if _, ok := accounts[user.CloudaccountId]; !ok { + account, err := user.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetCloudaccount")) + return + } + + cache, err := models.CloudgroupcacheManager.Register(group, account) + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "CloudgroupcacheManager.Register")) + return + } + + _, err = cache.GetOrCreateICloudgroup(ctx, self.GetUserCred()) + if err != nil { + self.taskFailed(ctx, group, errors.Wrap(err, "GetOrCreateICloudgroup")) + return + } + + result, err := cache.SyncCloudusersForCloud(ctx) + if err != nil { + logclient.AddActionLogWithStartable(self, cache, logclient.ACT_SYNC_POLICIES, err, self.UserCred, false) + continue + } + log.Infof("Sync cloudpolicies for group cache %s(%s) result: %s", cache.Name, cache.Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, group, result.AllError()) + return + } + } + } + } + group.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/cloudgroupcache_delete_task.go b/pkg/cloudid/tasks/cloudgroupcache_delete_task.go new file mode 100644 index 0000000000..b15665bb50 --- /dev/null +++ b/pkg/cloudid/tasks/cloudgroupcache_delete_task.go @@ -0,0 +1,72 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type CloudgroupcacheDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(CloudgroupcacheDeleteTask{}) +} + +func (self *CloudgroupcacheDeleteTask) taskFailed(ctx context.Context, cache *models.SCloudgroupcache, err error) { + cache.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, cache, logclient.ACT_DELETE, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *CloudgroupcacheDeleteTask) taskComplete(ctx context.Context, cache *models.SCloudgroupcache) { + cache.RealDelete(ctx, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, cache, logclient.ACT_DELETE, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} + +func (self *CloudgroupcacheDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + cache := obj.(*models.SCloudgroupcache) + if len(cache.ExternalId) == 0 { + self.taskComplete(ctx, cache) + return + } + iGroup, err := cache.GetICloudgroup() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + self.taskComplete(ctx, cache) + return + } + self.taskFailed(ctx, cache, errors.Wrap(err, "GetICloudgroup")) + return + } + err = iGroup.Delete() + if err != nil { + self.taskFailed(ctx, cache, errors.Wrap(err, "iGroup.Delete")) + return + } + self.taskComplete(ctx, cache) +} diff --git a/pkg/cloudid/tasks/cloudgroupcache_sync_status_task.go b/pkg/cloudid/tasks/cloudgroupcache_sync_status_task.go new file mode 100644 index 0000000000..6173c9ece7 --- /dev/null +++ b/pkg/cloudid/tasks/cloudgroupcache_sync_status_task.go @@ -0,0 +1,52 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type CloudgroupcacheSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(CloudgroupcacheSyncstatusTask{}) +} + +func (self *CloudgroupcacheSyncstatusTask) taskFailed(ctx context.Context, cache *models.SCloudgroupcache, err error) { + cache.SetStatus(self.GetUserCred(), api.CLOUD_GROUP_CACHE_STATUS_UNKNOWN, err.Error()) + logclient.AddActionLogWithStartable(self, cache, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *CloudgroupcacheSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + cache := obj.(*models.SCloudgroupcache) + _, err := cache.GetICloudgroup() + if err != nil { + self.taskFailed(ctx, cache, errors.Wrap(err, "GetICloudgroup")) + return + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/clouduser_delete_task.go b/pkg/cloudid/tasks/clouduser_delete_task.go new file mode 100644 index 0000000000..4d001b8724 --- /dev/null +++ b/pkg/cloudid/tasks/clouduser_delete_task.go @@ -0,0 +1,73 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type ClouduserDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ClouduserDeleteTask{}) +} + +func (self *ClouduserDeleteTask) taskFailed(ctx context.Context, clouduser *models.SClouduser, err error) { + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, clouduser, logclient.ACT_DELETE, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *ClouduserDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + clouduser := obj.(*models.SClouduser) + + if len(clouduser.ExternalId) == 0 { + clouduser.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) + return + } + + iUser, err := clouduser.GetIClouduser() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + clouduser.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) + return + } + self.taskFailed(ctx, clouduser, errors.Wrap(err, "GetIClouduser")) + return + } + + err = iUser.Delete() + if err != nil { + self.taskFailed(ctx, clouduser, errors.Wrap(err, "user.Delete")) + return + } + clouduser.RealDelete(ctx, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, clouduser, logclient.ACT_DELETE, clouduser, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/clouduser_reset_password_task.go b/pkg/cloudid/tasks/clouduser_reset_password_task.go new file mode 100644 index 0000000000..f8f1b05426 --- /dev/null +++ b/pkg/cloudid/tasks/clouduser_reset_password_task.go @@ -0,0 +1,80 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type ClouduserResetPasswordTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ClouduserResetPasswordTask{}) +} + +func (self *ClouduserResetPasswordTask) taskFailed(ctx context.Context, clouduser *models.SClouduser, err error) { + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_RESET_PASSWORD_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, clouduser, logclient.ACT_RESET_PASSWORD, err, self.UserCred, false) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *ClouduserResetPasswordTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + clouduser := obj.(*models.SClouduser) + password, _ := self.GetParams().GetString("password") + + account, err := clouduser.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, clouduser, errors.Wrap(err, "GetCloudaccount")) + return + } + + factory, err := account.GetProviderFactory() + if err != nil { + self.taskFailed(ctx, clouduser, errors.Wrap(err, "GetProviderFactory")) + return + } + + iUser, err := clouduser.GetIClouduser() + if err != nil { + self.taskFailed(ctx, clouduser, errors.Wrap(err, "GetIClouduser")) + return + } + + if factory.IsSupportResetClouduserPassword() { + err = iUser.ResetPassword(password) + if err != nil { + self.taskFailed(ctx, clouduser, errors.Wrap(err, "ResetPassword")) + return + } + clouduser.SyncWithClouduser(ctx, self.GetUserCred(), iUser, clouduser.CloudproviderId) + } else { + password = "" + } + + clouduser.SavePassword(password) + logclient.AddActionLogWithStartable(self, clouduser, logclient.ACT_RESET_PASSWORD, "", self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/clouduser_sync_groups_task.go b/pkg/cloudid/tasks/clouduser_sync_groups_task.go new file mode 100644 index 0000000000..875d6ff149 --- /dev/null +++ b/pkg/cloudid/tasks/clouduser_sync_groups_task.go @@ -0,0 +1,106 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type ClouduserSyncGroupsTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ClouduserSyncGroupsTask{}) +} + +func (self *ClouduserSyncGroupsTask) taskFailed(ctx context.Context, clouduser *models.SClouduser, err error) { + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_SYNC_GROUPS_FAILED, err.Error()) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *ClouduserSyncGroupsTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + user := obj.(*models.SClouduser) + + account, err := user.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetCloudaccount")) + return + } + + factory, err := account.GetProviderFactory() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetCloudaccount")) + return + } + + if factory.IsSupportCreateCloudgroup() { + groups, err := user.GetCloudgroups() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetCloudgroups")) + return + } + + for i := range groups { + cache, err := models.CloudgroupcacheManager.Register(&groups[i], account) + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "CloudgroupcacheManager.Register")) + return + } + _, err = cache.GetOrCreateICloudgroup(ctx, self.GetUserCred()) + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetOrCreateICloudgroup")) + return + } + result, err := cache.SyncCloudusersForCloud(ctx) + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetOrCreateICloudgroup")) + return + } + log.Infof("sync cloudusers for cache %s(%s) result: %s", cache.Name, cache.Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, user, result.AllError()) + return + } + } + } else if factory.IsSupportClouduserPolicy() { + result, err := user.SyncCloudpoliciesForCloud(ctx) + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "SyncCloudpoliciesForCloud")) + return + } + log.Infof("sync cloudpolicies for user %s(%s) result: %s", user.Name, user.Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, user, result.AllError()) + return + } + } + + if !self.IsSubtask() { + user.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_AVAILABLE, "") + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/clouduser_sync_policies_task.go b/pkg/cloudid/tasks/clouduser_sync_policies_task.go new file mode 100644 index 0000000000..db9e1a68df --- /dev/null +++ b/pkg/cloudid/tasks/clouduser_sync_policies_task.go @@ -0,0 +1,76 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type ClouduserSyncPoliciesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ClouduserSyncPoliciesTask{}) +} + +func (self *ClouduserSyncPoliciesTask) taskFailed(ctx context.Context, clouduser *models.SClouduser, err error) { + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_SYNC_POLICIES_FAILED, err.Error()) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *ClouduserSyncPoliciesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + user := obj.(*models.SClouduser) + + account, err := user.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetCloudaccount")) + return + } + + factory, err := account.GetProviderFactory() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "account.GetProviderFactory")) + return + } + + if factory.IsSupportClouduserPolicy() { + result, err := user.SyncCloudpoliciesForCloud(ctx) + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "SyncCloudpoliciesForCloud")) + return + } + log.Infof("sync cloudpolicies for user %s(%s) result: %s", user.Name, user.Id, result.Result()) + + if result.AddErrCnt+result.DelErrCnt > 0 { + self.taskFailed(ctx, user, result.AllError()) + return + } + } + + if !self.IsSubtask() { + user.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_AVAILABLE, "") + } + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/clouduser_sync_task.go b/pkg/cloudid/tasks/clouduser_sync_task.go new file mode 100644 index 0000000000..0cdf5a32cb --- /dev/null +++ b/pkg/cloudid/tasks/clouduser_sync_task.go @@ -0,0 +1,82 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type ClouduserSyncTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ClouduserSyncTask{}) +} + +func (self *ClouduserSyncTask) taskFailed(ctx context.Context, clouduser *models.SClouduser, err error) { + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_SYNC_FAILED, err.Error()) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *ClouduserSyncTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + user := obj.(*models.SClouduser) + + self.SetStage("OnSyncCloudpoliciesComplete", nil) + user.StartClouduserSyncPoliciesTask(ctx, self.GetUserCred(), self.GetTaskId()) +} + +func (self *ClouduserSyncTask) OnSyncCloudpoliciesComplete(ctx context.Context, user *models.SClouduser, body jsonutils.JSONObject) { + account, err := user.GetCloudaccount() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "user.GetCloudaccount")) + return + } + factory, err := account.GetProviderFactory() + if err != nil { + self.taskFailed(ctx, user, errors.Wrap(err, "GetProviderFactory")) + return + } + + if factory.IsSupportCreateCloudgroup() { + self.SetStage("OnSyncCloudgroupsComplete", nil) + user.StartClouduserSyncGroupsTask(ctx, self.GetUserCred(), self.GetTaskId()) + return + } + + user.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} + +func (self *ClouduserSyncTask) OnSyncCloudpoliciesCompleteFailed(ctx context.Context, user *models.SClouduser, data jsonutils.JSONObject) { + self.taskFailed(ctx, user, errors.Error(data.String())) +} + +func (self *ClouduserSyncTask) OnSyncCloudgroupsComplete(ctx context.Context, user *models.SClouduser, body jsonutils.JSONObject) { + user.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} + +func (self *ClouduserSyncTask) OnSyncCloudgroupsCompleteFailed(ctx context.Context, user *models.SClouduser, data jsonutils.JSONObject) { + self.taskFailed(ctx, user, errors.Error(data.String())) +} diff --git a/pkg/cloudid/tasks/clouduser_syncstatus_task.go b/pkg/cloudid/tasks/clouduser_syncstatus_task.go new file mode 100644 index 0000000000..46d85ce727 --- /dev/null +++ b/pkg/cloudid/tasks/clouduser_syncstatus_task.go @@ -0,0 +1,53 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type ClouduserSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ClouduserSyncstatusTask{}) +} + +func (self *ClouduserSyncstatusTask) taskFailed(ctx context.Context, clouduser *models.SClouduser, err error) { + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_UNKNOWN, err.Error()) + self.SetStageFailed(ctx, err.Error()) +} + +func (self *ClouduserSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + clouduser := obj.(*models.SClouduser) + + _, err := clouduser.GetIClouduser() + if err != nil { + self.taskFailed(ctx, clouduser, errors.Wrap(err, "GetIClouduser")) + return + } + + clouduser.SetStatus(self.GetUserCred(), api.CLOUD_USER_STATUS_AVAILABLE, "") + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/doc.go b/pkg/cloudid/tasks/doc.go new file mode 100644 index 0000000000..715bb89783 --- /dev/null +++ b/pkg/cloudid/tasks/doc.go @@ -0,0 +1 @@ +package tasks // import "yunion.io/x/onecloud/pkg/cloudid/tasks" diff --git a/pkg/cloudid/tasks/sync_cloudgroupcaches_task.go b/pkg/cloudid/tasks/sync_cloudgroupcaches_task.go new file mode 100644 index 0000000000..d65d6824fb --- /dev/null +++ b/pkg/cloudid/tasks/sync_cloudgroupcaches_task.go @@ -0,0 +1,60 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type SyncCloudgroupcachesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SyncCloudgroupcachesTask{}) +} + +func (self *SyncCloudgroupcachesTask) taskFailed(ctx context.Context, cloudaccount *models.SCloudaccount, err error) { + self.SetStageFailed(ctx, err.Error()) +} + +func (self *SyncCloudgroupcachesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + account := obj.(*models.SCloudaccount) + + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "GetProvider")) + return + } + + groups, err := provider.GetICloudgroups() + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "GetICloudgroups for %s(%s)", account.Name, account.Provider)) + return + } + + result := account.SyncCloudgroupcaches(ctx, self.GetUserCred(), groups) + log.Infof("Sync groups for %s(%s) result: %s", account.Name, account.Provider, result.Result()) + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/sync_cloudpolicies_task.go b/pkg/cloudid/tasks/sync_cloudpolicies_task.go new file mode 100644 index 0000000000..577a85e462 --- /dev/null +++ b/pkg/cloudid/tasks/sync_cloudpolicies_task.go @@ -0,0 +1,60 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" +) + +type SyncCloudpoliciesTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SyncCloudpoliciesTask{}) +} + +func (self *SyncCloudpoliciesTask) taskFailed(ctx context.Context, cloudaccount *models.SCloudaccount, err error) { + self.SetStageFailed(ctx, err.Error()) +} + +func (self *SyncCloudpoliciesTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + account := obj.(*models.SCloudaccount) + + provider, err := account.GetProvider() + if err != nil { + self.taskFailed(ctx, account, errors.Wrap(err, "GetProvider")) + return + } + + policy, err := provider.GetISystemCloudpolicies() + if err != nil { + self.taskFailed(ctx, account, errors.Wrapf(err, "GetISystemCloudpolicies for %s(%s)", account.Name, account.Provider)) + return + } + + result := account.SyncCloudpolicies(ctx, self.GetUserCred(), policy) + log.Infof("Sync policies for %s(%s) result: %s", account.Name, account.Provider, result.Result()) + + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/cloudid/tasks/sync_cloudusers_task.go b/pkg/cloudid/tasks/sync_cloudusers_task.go new file mode 100644 index 0000000000..1265b93284 --- /dev/null +++ b/pkg/cloudid/tasks/sync_cloudusers_task.go @@ -0,0 +1,125 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudid/models" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SyncCloudusersTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(SyncCloudusersTask{}) +} + +type IProvider interface { + GetProvider() (cloudprovider.ICloudProvider, error) + GetName() string + GetCloudproviderId() string +} + +func (self *SyncCloudusersTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + account := obj.(*models.SCloudaccount) + self.SetStage("OnClouduserSyncComplete", nil) + taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) { + factory, err := account.GetProviderFactory() + if err != nil { + return nil, errors.Wrap(err, "account.GetProviderFactory") + } + if !factory.IsSupportClouduser() { + return nil, nil + } + iProviders := []IProvider{account} + if factory.IsClouduserBelongCloudprovider() { + iProviders = []IProvider{} + providers, err := account.GetCloudproviders() + if err != nil { + return nil, errors.Wrap(err, "GetCloudproviders") + } + for i := range providers { + iProviders = append(iProviders, &providers[i]) + } + } + for i := range iProviders { + provider, err := iProviders[i].GetProvider() + if err != nil { + return nil, errors.Wrap(err, "GetProvider") + } + iUsers, err := provider.GetICloudusers() + if err != nil { + return nil, errors.Wrap(err, "provider.GetICloudusers") + } + localUsers, remoteUsers, result := account.SyncCloudusers(ctx, self.UserCred, iProviders[i].GetCloudproviderId(), iUsers) + msg := fmt.Sprintf("SyncCloudusers for account %s(%s) result: %s", iProviders[i].GetName(), account.Provider, result.Result()) + log.Infof(msg) + + for i := 0; i < len(localUsers); i += 1 { + func() { + // lock clouduser + lockman.LockObject(ctx, &localUsers[i]) + defer lockman.ReleaseObject(ctx, &localUsers[i]) + + syncClouduserPolicies(ctx, self.GetUserCred(), &localUsers[i], remoteUsers[i]) + syncClouduserGroups(ctx, self.GetUserCred(), &localUsers[i], remoteUsers[i]) + }() + } + } + return nil, nil + }) +} + +func syncClouduserPolicies(ctx context.Context, userCred mcclient.TokenCredential, localUser *models.SClouduser, remoteUser cloudprovider.IClouduser) { + iPolicies, err := remoteUser.GetISystemCloudpolicies() + if err != nil { + log.Errorf("failed to get user %s policies error: %v", remoteUser.GetName(), err) + return + } + result := localUser.SyncCloudpolicies(ctx, userCred, iPolicies) + msg := fmt.Sprintf("SyncCloudpolicies for user %s(%s) result: %s", localUser.Name, localUser.Id, result.Result()) + log.Infof(msg) +} + +func syncClouduserGroups(ctx context.Context, userCred mcclient.TokenCredential, localUser *models.SClouduser, remoteUser cloudprovider.IClouduser) { + iGroups, err := remoteUser.GetICloudgroups() + if err != nil { + log.Errorf("failed to get user %s groups error: %v", remoteUser.GetName(), err) + return + } + result := localUser.SyncCloudgroups(ctx, userCred, iGroups) + msg := fmt.Sprintf("SyncCloudgroups for user %s(%s) result: %s", localUser.Name, localUser.Id, result.Result()) + log.Infof(msg) +} + +func (self *SyncCloudusersTask) OnClouduserSyncComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageComplete(ctx, nil) +} + +func (self *SyncCloudusersTask) OnClouduserSyncCompleteFailed(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStageFailed(ctx, data.String()) +} diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index e6589fba9b..02978ab676 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -32,6 +32,8 @@ import ( const ( ErrNoSuchProvder = errors.Error("no such provider") + + TEST_CLOUDID_USER_NAME = "cloud-id-user" ) type SCloudaccountCredential struct { @@ -190,6 +192,14 @@ type ICloudProviderFactory interface { IsCloudeventRegional() bool GetMaxCloudEventSyncDays() int GetMaxCloudEventKeepDays() int + + IsSupportClouduser() bool + IsSupportClouduserPolicy() bool + IsSupportResetClouduserPassword() bool + GetClouduserMinPolicyCount() int + IsClouduserNeedInitPolicy() bool + IsClouduserBelongCloudprovider() bool + IsSupportCreateCloudgroup() bool } type ICloudProvider interface { @@ -197,6 +207,8 @@ type ICloudProvider interface { GetSysInfo() (jsonutils.JSONObject, error) GetVersion() string + GetIamLoginUrl() string + IsSupportCloudId() bool GetIRegions() []ICloudRegion GetIProjects() ([]ICloudProject, error) @@ -218,6 +230,15 @@ type ICloudProvider interface { GetCapabilities() []string GetICloudQuotas() ([]ICloudQuota, error) + + IsClouduserSupportPassword() bool + GetICloudusers() ([]IClouduser, error) + GetISystemCloudpolicies() ([]ICloudpolicy, error) + GetICloudgroups() ([]ICloudgroup, error) + GetICloudgroupByName(name string) (ICloudgroup, error) + CreateICloudgroup(name, desc string) (ICloudgroup, error) + GetIClouduserByName(name string) (IClouduser, error) + CreateIClouduser(conf *SClouduserCreateConfig) (IClouduser, error) } func IsSupportProject(prod ICloudProvider) bool { @@ -321,6 +342,64 @@ func (self *SBaseProvider) GetICloudQuotas() ([]ICloudQuota, error) { return nil, ErrNotImplemented } +func (self *SBaseProvider) GetIamLoginUrl() string { + return "" +} + +func (self *SBaseProvider) IsSupportCloudId() bool { + return false +} + +func IsSupportCloudId(provider ICloudProvider) bool { + defer func() { + iUser, err := provider.GetIClouduserByName(TEST_CLOUDID_USER_NAME) + if err != nil { + return + } + err = iUser.Delete() + if err != nil { + log.Errorf("failed to delete test user: %v", err) + } + }() + _, err := provider.CreateIClouduser(&SClouduserCreateConfig{Name: TEST_CLOUDID_USER_NAME}) + if err != nil { + return false + } + return true +} + +func (self *SBaseProvider) IsClouduserSupportPassword() bool { + return true +} + +func (self *SBaseProvider) GetICloudusers() ([]IClouduser, error) { + return nil, ErrNotImplemented +} + +func (self *SBaseProvider) GetICloudgroups() ([]ICloudgroup, error) { + return nil, ErrNotImplemented +} + +func (self *SBaseProvider) GetICloudgroupByName(name string) (ICloudgroup, error) { + return nil, ErrNotImplemented +} + +func (self *SBaseProvider) CreateICloudgroup(name, desc string) (ICloudgroup, error) { + return nil, ErrNotImplemented +} + +func (self *SBaseProvider) GetISystemCloudpolicies() ([]ICloudpolicy, error) { + return nil, ErrNotImplemented +} + +func (self *SBaseProvider) GetIClouduserByName(name string) (IClouduser, error) { + return nil, ErrNotImplemented +} + +func (self *SBaseProvider) CreateIClouduser(conf *SClouduserCreateConfig) (IClouduser, error) { + return nil, ErrNotImplemented +} + func (self *SBaseProvider) GetCloudRegionExternalIdPrefix() string { return self.factory.GetId() } @@ -353,6 +432,16 @@ func GetPrivateProviders() []string { return providers } +func GetSupportCloudgroupProviders() []string { + providers := []string{} + for p, d := range providerTable { + if d.IsSupportCreateCloudgroup() { + providers = append(providers, p) + } + } + return providers +} + func GetOnPremiseProviders() []string { providers := make([]string, 0) for p, d := range providerTable { @@ -408,6 +497,35 @@ func (factory *baseProviderFactory) GetMaxCloudEventKeepDays() int { return 7 } +func (factory *baseProviderFactory) IsSupportClouduser() bool { + return false +} + +func (factory *baseProviderFactory) IsSupportClouduserPolicy() bool { + return true +} + +func (factory *baseProviderFactory) IsSupportResetClouduserPassword() bool { + return true +} + +func (factory *baseProviderFactory) IsClouduserNeedInitPolicy() bool { + return false +} + +func (factory *baseProviderFactory) GetClouduserMinPolicyCount() int { + // unlimited + return -1 +} + +func (factory *baseProviderFactory) IsSupportCreateCloudgroup() bool { + return false +} + +func (factory *baseProviderFactory) IsClouduserBelongCloudprovider() bool { + return false +} + type SPremiseBaseProviderFactory struct { baseProviderFactory } diff --git a/pkg/cloudprovider/clouduser.go b/pkg/cloudprovider/clouduser.go new file mode 100644 index 0000000000..408abc290e --- /dev/null +++ b/pkg/cloudprovider/clouduser.go @@ -0,0 +1,25 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloudprovider + +type SClouduserCreateConfig struct { + Name string + Desc string + Password string + IsConsoleLogin bool + Email string + MobilePhone string + ExternalPolicyIds []string +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index d39dbd9dbf..32490235cc 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -952,3 +952,44 @@ type ICloudQuota interface { GetMaxQuotaCount() int GetCurrentQuotaUsedCount() int } + +// 公有云子账号 +type IClouduser interface { + GetGlobalId() string + GetName() string + + GetICloudgroups() ([]ICloudgroup, error) + + GetISystemCloudpolicies() ([]ICloudpolicy, error) + AttachSystemPolicy(policyType string) error + DetachSystemPolicy(policyId string) error + Delete() error + + ResetPassword(password string) error + IsConsoleLogin() bool +} + +// 公有云子账号权限 +type ICloudpolicy interface { + GetGlobalId() string + GetName() string + //GetPolicyType() string + GetDescription() string +} + +// 公有云用户组 +type ICloudgroup interface { + GetGlobalId() string + GetName() string + GetDescription() string + GetISystemCloudpolicies() ([]ICloudpolicy, error) + GetICloudusers() ([]IClouduser, error) + + AddUser(name string) error + RemoveUser(name string) error + + AttachSystemPolicy(policyId string) error + DetachSystemPolicy(policyId string) error + + Delete() error +} diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index e3315f0a4b..444fd258ac 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -160,6 +160,12 @@ type SCloudaccount struct { // 默认值proxyapi.ProxySettingId_DIRECT ProxySettingId string `width:"36" charset:"ascii" nullable:"false" list:"domain" create:"optional" update:"domain" default:"DIRECT"` + + // 公有云子账号登录地址 + IamLoginUrl string `width:"512" charset:"ascii" nullable:"false" list:"domain" update:"domain"` + + // 是否支持创建公有云子账号 + IsSupportCloudId tristate.TriState `nullable:"false" get:"domain" list:"domain" default:"false"` } func (self *SCloudaccount) GetCloudproviders() []SCloudprovider { @@ -2269,6 +2275,7 @@ func (account *SCloudaccount) probeAccountStatus(ctx context.Context, userCred m log.Errorf("manager.GetSysInfo fail %s", err) return nil, errors.Wrap(err, "manager.GetSysInfo") } + iamLoginUrl := manager.GetIamLoginUrl() factory := manager.GetFactory() diff, err := db.Update(account, func() error { isPublic := factory.IsPublicCloud() @@ -2282,6 +2289,8 @@ func (account *SCloudaccount) probeAccountStatus(ctx context.Context, userCred m account.ProbeAt = timeutils.UtcNow() account.Version = version account.Sysinfo = sysInfo + account.IamLoginUrl = iamLoginUrl + account.IsSupportCloudId = tristate.NewFromBool(manager.IsSupportCloudId()) return nil }) if err != nil { diff --git a/pkg/compute/models/keypairs.go b/pkg/compute/models/keypairs.go index 46e0ffa10b..2edc7a94d5 100644 --- a/pkg/compute/models/keypairs.go +++ b/pkg/compute/models/keypairs.go @@ -20,7 +20,6 @@ import ( "golang.org/x/crypto/ssh" "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" @@ -36,14 +35,14 @@ import ( ) type SKeypairManager struct { - db.SStandaloneResourceBaseManager + db.SUserResourceBaseManager } var KeypairManager *SKeypairManager func init() { KeypairManager = &SKeypairManager{ - SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SUserResourceBaseManager: db.NewUserResourceBaseManager( SKeypair{}, "keypairs_tbl", "keypair", @@ -54,7 +53,7 @@ func init() { } type SKeypair struct { - db.SStandaloneResourceBase + db.SUserResourceBase // 加密类型 // example: RSA @@ -66,8 +65,6 @@ type SKeypair struct { PrivateKey string `width:"2048" charset:"ascii" nullable:"true" create:"optional"` // 公钥 PublicKey string `width:"1024" charset:"ascii" nullable:"false" list:"user" create:"required"` - // 用户Id - OwnerId string `width:"128" charset:"ascii" index:"true" nullable:"false" create:"required"` } // 列出ssh密钥对 @@ -77,22 +74,10 @@ func (manager *SKeypairManager) ListItemFilter( userCred mcclient.TokenCredential, query api.KeypairListInput, ) (*sqlchemy.SQuery, error) { - q, err := manager.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput) + q, err := manager.SUserResourceBaseManager.ListItemFilter(ctx, q, userCred, query.UserResourceListInput) if err != nil { return nil, err } - if query.Admin != nil && *query.Admin && db.IsAdminAllowList(userCred, manager) { - user := query.User - if len(user) > 0 { - uc, _ := db.UserCacheManager.FetchUserByIdOrName(ctx, user) - if uc == nil { - return nil, httperrors.NewUserNotFoundError("user %s not found", user) - } - q = q.Equals("owner_id", uc.Id) - } - } else { - q = q.Equals("owner_id", userCred.GetUserId()) - } if len(query.Scheme) > 0 { q = q.In("scheme", query.Scheme) @@ -111,34 +96,22 @@ func (manager *SKeypairManager) OrderByExtraFields( query api.KeypairListInput, ) (*sqlchemy.SQuery, error) { var err error - q, err = manager.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput) + q, err = manager.SUserResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.UserResourceListInput) if err != nil { - return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields") + return nil, errors.Wrap(err, "SUserResourceBaseManager.OrderByExtraFields") } return q, nil } func (manager *SKeypairManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { var err error - q, err = manager.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + q, err = manager.SUserResourceBaseManager.QueryDistinctExtraField(q, field) if err == nil { return q, nil } return q, httperrors.ErrNotFound } -func (manager *SKeypairManager) AllowListItems(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return true -} - -func (self *SKeypair) IsOwner(userCred mcclient.TokenCredential) bool { - return self.OwnerId == userCred.GetUserId() -} - -func (self *SKeypair) AllowGetDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { - return self.IsOwner(userCred) || db.IsAdminAllowGet(userCred, self) -} - func (self *SKeypair) GetExtraDetails( ctx context.Context, userCred mcclient.TokenCredential, @@ -157,57 +130,30 @@ func (manager *SKeypairManager) FetchCustomizeColumns( isList bool, ) []api.KeypairDetails { rows := make([]api.KeypairDetails, len(objs)) - stdRows := manager.SStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) - userIds := make([]string, len(objs)) + userRows := manager.SUserResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) for i := range rows { keypair := objs[i].(*SKeypair) rows[i] = api.KeypairDetails{ - StandaloneResourceDetails: stdRows[i], - PrivateKeyLen: len(keypair.PrivateKey), + UserResourceDetails: userRows[i], + PrivateKeyLen: len(keypair.PrivateKey), } rows[i].LinkedGuestCount, _ = keypair.GetLinkedGuestsCount() - userIds[i] = keypair.OwnerId - } - - users := make(map[string]db.SUser) - err := db.FetchStandaloneObjectsByIds(db.UserCacheManager, userIds, &users) - if err != nil { - log.Errorf("FetchStandaloneObjectsByIds for users fail %s", err) - return rows - } - - for i := range rows { - if owner, ok := users[userIds[i]]; ok { - rows[i].OwnerName = owner.Name - } } return rows } -func (manager *SKeypairManager) AllowCreateItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return true -} - -func (self *SKeypair) AllowUpdateItem(ctx context.Context, userCred mcclient.TokenCredential) bool { - return self.IsOwner(userCred) || db.IsAdminAllowUpdate(userCred, self) -} - -func (self *SKeypair) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { - return self.IsOwner(userCred) || db.IsAdminAllowDelete(userCred, self) -} - func (self *SKeypair) GetLinkedGuestsCount() (int, error) { return GuestManager.Query().Equals("keypair_id", self.Id).CountWithError() } -func (manager *SKeypairManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.KeypairCreateInput) (*jsonutils.JSONDict, error) { +func (manager *SKeypairManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.KeypairCreateInput) (api.KeypairCreateInput, error) { if len(input.PublicKey) == 0 { if len(input.Scheme) == 0 { input.Scheme = api.KEYPAIRE_SCHEME_RSA } if !utils.IsInStringArray(input.Scheme, api.KEYPAIR_SCHEMAS) { - return nil, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) + return input, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) } var err error @@ -217,28 +163,26 @@ func (manager *SKeypairManager) ValidateCreateData(ctx context.Context, userCred input.PrivateKey, input.PublicKey, err = seclib2.GenerateDSASSHKeypair() } if err != nil { - return nil, httperrors.NewGeneralError(errors.Wrapf(err, "Generate%sSSHKeypair", input.Scheme)) + return input, httperrors.NewGeneralError(errors.Wrapf(err, "Generate%sSSHKeypair", input.Scheme)) } } pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(input.PublicKey)) if err != nil { - return nil, httperrors.NewInputParameterError("invalid public error: %v", err) + return input, httperrors.NewInputParameterError("invalid public error: %v", err) } // 只允许上传RSA格式密钥。PS: AWS只支持RSA格式。 input.Scheme = seclib2.GetPublicKeyScheme(pubKey) if input.Scheme != api.KEYPAIRE_SCHEME_RSA { - return nil, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) + return input, httperrors.NewInputParameterError("Unsupported scheme %s", input.Scheme) } input.Fingerprint = ssh.FingerprintLegacyMD5(pubKey) - input.OwnerId = userCred.GetUserId() - - input.StandaloneResourceCreateInput, err = manager.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.StandaloneResourceCreateInput) + input.UserResourceCreateInput, err = manager.SUserResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.UserResourceCreateInput) if err != nil { - return nil, err + return input, err } - return input.JSON(input), nil + return input, nil } func (self *SKeypair) ValidateDeleteCondition(ctx context.Context) error { @@ -268,19 +212,6 @@ func (manager *SKeypairManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient return q } -func (self *SKeypair) GetOwnerId() mcclient.IIdentityProvider { - owner := db.SOwnerId{UserId: self.OwnerId} - return &owner -} - -func (manager *SKeypairManager) FetchByName(userCred mcclient.IIdentityProvider, idStr string) (db.IModel, error) { - return db.FetchByName(manager, userCred, idStr) -} - -func (manager *SKeypairManager) FetchByIdOrName(userCred mcclient.IIdentityProvider, idStr string) (db.IModel, error) { - return db.FetchByIdOrName(manager, userCred, idStr) -} - func (keypair *SKeypair) AllowGetDetailsPrivatekey(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { return keypair.OwnerId == userCred.GetUserId() } @@ -304,15 +235,3 @@ func (keypair *SKeypair) GetDetailsPrivatekey(ctx context.Context, userCred mccl } return retval, nil } - -func (manager *SKeypairManager) FetchOwnerId(ctx context.Context, data jsonutils.JSONObject) (mcclient.IIdentityProvider, error) { - return db.FetchUserInfo(ctx, data) -} - -func (manager *SKeypairManager) NamespaceScope() rbacutils.TRbacScope { - return rbacutils.ScopeUser -} - -func (manager *SKeypairManager) ResourceScope() rbacutils.TRbacScope { - return rbacutils.ScopeUser -} diff --git a/pkg/compute/models/loadbalanceracls.go b/pkg/compute/models/loadbalanceracls.go index be3be9f624..2167d24d8d 100644 --- a/pkg/compute/models/loadbalanceracls.go +++ b/pkg/compute/models/loadbalanceracls.go @@ -424,7 +424,7 @@ func (man *SLoadbalancerAclManager) SyncLoadbalancerAcls(ctx context.Context, us func (manager *SLoadbalancerAclManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SLoadbalancerAclManager) InitializeData() error { diff --git a/pkg/compute/models/loadbalancerbackendgroups.go b/pkg/compute/models/loadbalancerbackendgroups.go index 67203a5752..be33f04520 100644 --- a/pkg/compute/models/loadbalancerbackendgroups.go +++ b/pkg/compute/models/loadbalancerbackendgroups.go @@ -1078,7 +1078,7 @@ func (manager *SLoadbalancerBackendGroupManager) initBackendGroupRegion() error func (manager *SLoadbalancerBackendGroupManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SLoadbalancerBackendGroupManager) ListItemExportKeys(ctx context.Context, diff --git a/pkg/compute/models/loadbalancerbackends.go b/pkg/compute/models/loadbalancerbackends.go index e6599fdbf8..ce7282d141 100644 --- a/pkg/compute/models/loadbalancerbackends.go +++ b/pkg/compute/models/loadbalancerbackends.go @@ -648,7 +648,7 @@ func (manager *SLoadbalancerBackendManager) InitializeData() error { func (manager *SLoadbalancerBackendManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SLoadbalancerBackendManager) ListItemExportKeys(ctx context.Context, diff --git a/pkg/compute/models/loadbalancercertificates.go b/pkg/compute/models/loadbalancercertificates.go index 93a2413c88..fa129f0f32 100644 --- a/pkg/compute/models/loadbalancercertificates.go +++ b/pkg/compute/models/loadbalancercertificates.go @@ -402,5 +402,5 @@ func (man *SLoadbalancerCertificateManager) CreateCertificate(ctx context.Contex func (manager *SLoadbalancerCertificateManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } diff --git a/pkg/compute/models/loadbalancerlistenerrules.go b/pkg/compute/models/loadbalancerlistenerrules.go index 1bffd106d6..afad5489ef 100644 --- a/pkg/compute/models/loadbalancerlistenerrules.go +++ b/pkg/compute/models/loadbalancerlistenerrules.go @@ -941,7 +941,7 @@ func (lbr *SLoadbalancerListenerRule) SyncWithCloudLoadbalancerListenerRule( func (manager *SLoadbalancerListenerRuleManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SLoadbalancerListenerRuleManager) ListItemExportKeys(ctx context.Context, diff --git a/pkg/compute/models/loadbalancerlisteners.go b/pkg/compute/models/loadbalancerlisteners.go index 10a0dd9ee5..98f2f8bc50 100644 --- a/pkg/compute/models/loadbalancerlisteners.go +++ b/pkg/compute/models/loadbalancerlisteners.go @@ -1236,7 +1236,7 @@ func (manager *SLoadbalancerListenerManager) InitializeData() error { func (manager *SLoadbalancerListenerManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SLoadbalancerListenerManager) ListItemExportKeys(ctx context.Context, diff --git a/pkg/compute/models/loadbalancers.go b/pkg/compute/models/loadbalancers.go index 129434dbed..7a801cd1e9 100644 --- a/pkg/compute/models/loadbalancers.go +++ b/pkg/compute/models/loadbalancers.go @@ -1034,7 +1034,7 @@ func (man *SLoadbalancerManager) InitializeData() error { func (manager *SLoadbalancerManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("pending_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SLoadbalancerManager) FetchByExternalId(providerId string, extId string) (*SLoadbalancer, error) { diff --git a/pkg/compute/models/snapshots.go b/pkg/compute/models/snapshots.go index d390409a9c..2ed6fee5c9 100644 --- a/pkg/compute/models/snapshots.go +++ b/pkg/compute/models/snapshots.go @@ -1015,7 +1015,7 @@ func (self *SSnapshot) getCloudProviderInfo() SCloudProviderInfo { func (manager *SSnapshotManager) GetResourceCount() ([]db.SScopeResourceCount, error) { virts := manager.Query().IsFalse("fake_deleted") - return db.CalculateProjectResourceCount(virts) + return db.CalculateResourceCount(virts, "tenant_id") } func (manager *SSnapshotManager) CleanupSnapshots(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index f560726bf7..696c8026c2 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -34,7 +34,7 @@ func InitHandlers(app *appsrv.Application) { db.RegistUserCredCacheUpdater() - db.AddProjectResourceCountHandler("", app) + db.AddScopeResourceCountHandler("", app) quotas.AddQuotaHandler(&models.QuotaManager.SQuotaBaseManager, "", app) quotas.AddQuotaHandler(&models.RegionQuotaManager.SQuotaBaseManager, "", app) diff --git a/pkg/image/service/handlers.go b/pkg/image/service/handlers.go index 7f795e65bd..57bf3db052 100644 --- a/pkg/image/service/handlers.go +++ b/pkg/image/service/handlers.go @@ -36,7 +36,7 @@ func InitHandlers(app *appsrv.Application) { db.RegistUserCredCacheUpdater() - db.AddProjectResourceCountHandler(API_VERSION, app) + db.AddScopeResourceCountHandler(API_VERSION, app) quotas.AddQuotaHandler(&models.QuotaManager.SQuotaBaseManager, API_VERSION, app) usages.AddUsageHandler(API_VERSION, app) diff --git a/pkg/keystone/cronjobs/project_resources.go b/pkg/keystone/cronjobs/project_resources.go index 6dbd3be3f9..b79567b0a6 100644 --- a/pkg/keystone/cronjobs/project_resources.go +++ b/pkg/keystone/cronjobs/project_resources.go @@ -22,6 +22,7 @@ import ( "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/keystone/models" @@ -45,16 +46,16 @@ type sServiceEndpoints struct { external string } -func FetchProjectResourceCount(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { - log.Debugf("FetchProjectResourceCount") +func FetchScopeResourceCount(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) { + log.Debugf("FetchScopeResourceCount") eps, err := models.EndpointManager.FetchAll() if err != nil { return } serviceTbl := make(map[string]*sServiceEndpoints) for _, ep := range eps { - if ep.ServiceType == api.SERVICE_TYPE { - // skip self + if ep.ServiceType == apis.SERVICE_TYPE_KEYSTONE || ep.ServiceType == apis.SERVICE_TYPE_OFFLINE_CLOUDMETA { + // skip self and offline cloudmeta continue } key := fmt.Sprintf("%s-%s", ep.RegionId, ep.ServiceId) @@ -81,11 +82,10 @@ func FetchProjectResourceCount(ctx context.Context, userCred mcclient.TokenCrede if url == "" { url = ep.external } - url = httputils.JoinPath(url, "project-resources") + url = httputils.JoinPath(url, "scope-resources") tk, _ := tokens.GetDefaultToken() hdr := http.Header{} hdr.Add("X-Auth-Token", tk) - // log.Debugf("request %s", url) _, ret, err := httputils.JSONRequest( httputils.GetDefaultClient(), ctx, "GET", @@ -111,48 +111,57 @@ func FetchProjectResourceCount(ctx context.Context, userCred mcclient.TokenCrede if err != nil { continue } - syncProjectResourceCount(ctx, ep.regionId, ep.serviceId, projectResCounts) + syncScopeResourceCount(ctx, ep.regionId, ep.serviceId, projectResCounts) } } -func syncProjectResourceCount(ctx context.Context, regionId string, serviceId string, projResCnt map[string][]db.SScopeResourceCount) { +func syncScopeResourceCount(ctx context.Context, regionId string, serviceId string, projResCnt map[string][]db.SScopeResourceCount) { projList := make([]string, 0) + domainList := []string{} + ownerList := []string{} for res, resCnts := range projResCnt { for i := range resCnts { - if len(resCnts[i].TenantId) == 0 && len(resCnts[i].DomainId) == 0 { + if len(resCnts[i].TenantId) == 0 && len(resCnts[i].DomainId) == 0 && len(resCnts[i].OwnerId) == 0 { continue } - projRes := models.SProjectResource{} - if len(resCnts[i].TenantId) > 0 { - projRes.ProjectId = resCnts[i].TenantId - } else { - projRes.ProjectId = resCnts[i].DomainId + scopeRes := models.SScopeResource{ + DomainId: resCnts[i].DomainId, + ProjectId: resCnts[i].TenantId, + OwnerId: resCnts[i].OwnerId, } - projRes.RegionId = regionId - projRes.ServiceId = serviceId - projRes.Resource = res - projRes.Count = resCnts[i].ResCount + scopeRes.RegionId = regionId + scopeRes.ServiceId = serviceId + scopeRes.Resource = res + scopeRes.Count = resCnts[i].ResCount - projList = append(projList, projRes.ProjectId) + projList = append(projList, scopeRes.ProjectId) + domainList = append(domainList, scopeRes.DomainId) + ownerList = append(ownerList, scopeRes.OwnerId) - err := models.ProjectResourceManager.TableSpec().InsertOrUpdate(ctx, &projRes) + err := models.ScopeResourceManager.TableSpec().InsertOrUpdate(ctx, &scopeRes) if err != nil { log.Errorf("table insert error %s", err) } } - q := models.ProjectResourceManager.Query() + q := models.ScopeResourceManager.Query() if len(projList) > 0 { q = q.NotIn("project_id", projList) } + if len(domainList) > 0 { + q = q.NotIn("domain_id", domainList) + } + if len(ownerList) > 0 { + q = q.NotIn("owner_id", ownerList) + } q = q.Equals("region_id", regionId) q = q.Equals("service_id", serviceId) q = q.Equals("resource", res) q = q.NotEquals("count", 0) - emptySets := make([]models.SProjectResource, 0) - err := db.FetchModelObjects(models.ProjectResourceManager, q, &emptySets) + emptySets := make([]models.SScopeResource, 0) + err := db.FetchModelObjects(models.ScopeResourceManager, q, &emptySets) if err != nil { log.Errorf("db.FetchModelObjects %s", err) } diff --git a/pkg/keystone/models/domains.go b/pkg/keystone/models/domains.go index d535be3650..c799643dcf 100644 --- a/pkg/keystone/models/domains.go +++ b/pkg/keystone/models/domains.go @@ -359,7 +359,7 @@ func (manager *SDomainManager) FetchCustomizeColumns( if update.IsZero() { update = time.Now() } - nextUpdate := update.Add(time.Duration(options.Options.FetchProjectResourceCountIntervalSeconds) * time.Second) + nextUpdate := update.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second) rows[i].ExtResourcesNextUpdate = nextUpdate } } @@ -485,7 +485,7 @@ func (domain *SDomain) UnlinkIdp(idpId string) error { } func (domain *SDomain) getExternalResources() (map[string]int, time.Time, error) { - return ProjectResourceManager.getProjectResource(domain.Id) + return ScopeResourceManager.getScopeResource(domain.Id, "", "") } func (manager *SDomainManager) ValidateCreateData( diff --git a/pkg/keystone/models/project_resources.go b/pkg/keystone/models/project_resources.go index 107e084111..4849217e29 100644 --- a/pkg/keystone/models/project_resources.go +++ b/pkg/keystone/models/project_resources.go @@ -24,28 +24,30 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" ) -type SProjectResourceManager struct { +type SScopeResourceManager struct { db.SModelBaseManager } -var ProjectResourceManager *SProjectResourceManager +var ScopeResourceManager *SScopeResourceManager func init() { - ProjectResourceManager = &SProjectResourceManager{ + ScopeResourceManager = &SScopeResourceManager{ SModelBaseManager: db.NewModelBaseManager( - SProjectResource{}, - "project_resource_tbl", - "project_resource", - "project_resources", + SScopeResource{}, + "scope_resource_tbl", + "scope_resource", + "scope_resources", ), } - ProjectResourceManager.SetVirtualObject(ProjectResourceManager) + ScopeResourceManager.SetVirtualObject(ScopeResourceManager) } -type SProjectResource struct { +type SScopeResource struct { db.SModelBase + DomainId string `width:"64" charset:"ascii" primary:"true"` ProjectId string `width:"64" charset:"ascii" primary:"true"` + OwnerId string `width:"64" charset:"ascii" primary:"true"` RegionId string `width:"32" charset:"ascii" primary:"true"` ServiceId string `width:"32" charset:"ascii" primary:"true"` Resource string `width:"32" charset:"ascii" primary:"true"` @@ -53,22 +55,30 @@ type SProjectResource struct { UpdatedAt time.Time `nullable:"true" updated_at:"true"` } -type sProjectResourceCount struct { +type sScopeResourceCount struct { Resource string ResCount int LastUpdate time.Time } -func (manager *SProjectResourceManager) getProjectResource(projId string) (map[string]int, time.Time, error) { +func (manager *SScopeResourceManager) getScopeResource(domainId, projId, ownerId string) (map[string]int, time.Time, error) { resources := manager.Query().SubQuery() q := resources.Query( resources.Field("resource"), sqlchemy.SUM("res_count", resources.Field("count")), sqlchemy.MAX("last_update", resources.Field("updated_at")), ) - q = q.Filter(sqlchemy.Equals(resources.Field("project_id"), projId)) + if len(domainId) > 0 { + q = q.Filter(sqlchemy.Equals(resources.Field("domain_id"), domainId)) + } + if len(projId) > 0 { + q = q.Filter(sqlchemy.Equals(resources.Field("project_id"), projId)) + } + if len(ownerId) > 0 { + q = q.Filter(sqlchemy.Equals(resources.Field("owner_id"), ownerId)) + } q = q.GroupBy(resources.Field("resource")) - resCnts := make([]sProjectResourceCount, 0) + resCnts := make([]sScopeResourceCount, 0) err := q.All(&resCnts) if err != nil && err != sql.ErrNoRows { return nil, time.Time{}, errors.Wrap(err, "query.All") diff --git a/pkg/keystone/models/projects.go b/pkg/keystone/models/projects.go index 55d54bbe3c..ac68aba350 100644 --- a/pkg/keystone/models/projects.go +++ b/pkg/keystone/models/projects.go @@ -375,14 +375,14 @@ func projectExtra(proj *SProject, out api.ProjectDetails) api.ProjectDetails { if update.IsZero() { update = time.Now() } - nextUpdate := update.Add(time.Duration(options.Options.FetchProjectResourceCountIntervalSeconds) * time.Second) + nextUpdate := update.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second) out.ExtResourcesNextUpdate = nextUpdate } return out } func (proj *SProject) getExternalResources() (map[string]int, time.Time, error) { - return ProjectResourceManager.getProjectResource(proj.Id) + return ScopeResourceManager.getScopeResource("", proj.Id, "") } func NormalizeProjectName(name string) string { diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index 4fc21a353c..a5e6f17e47 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -32,6 +32,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/keystone/options" o "yunion.io/x/onecloud/pkg/keystone/options" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/logclient" @@ -605,6 +606,16 @@ func userExtra(user *SUser, out api.UserDetails) api.UserDetails { } } + external, update, _ := user.getExternalResources() + if len(external) > 0 { + out.ExtResource = jsonutils.Marshal(external) + out.ExtResourcesLastUpdate = update + if update.IsZero() { + update = time.Now() + } + nextUpdate := update.Add(time.Duration(options.Options.FetchScopeResourceCountIntervalSeconds) * time.Second) + out.ExtResourcesNextUpdate = nextUpdate + } return out } @@ -664,9 +675,17 @@ func (user *SUser) ValidateDeleteCondition(ctx context.Context) error { if user.IsReadOnly() { return httperrors.NewForbiddenError("readonly") } + external, _, _ := user.getExternalResources() + if len(external) > 0 { + return httperrors.NewNotEmptyError("user contains external resources") + } return user.SIdentityBaseResource.ValidateDeleteCondition(ctx) } +func (user *SUser) getExternalResources() (map[string]int, time.Time, error) { + return ScopeResourceManager.getScopeResource("", "", user.Id) +} + func (user *SUser) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { err := AssignmentManager.projectRemoveAllUser(ctx, userCred, user) if err != nil { diff --git a/pkg/keystone/options/options.go b/pkg/keystone/options/options.go index d711b8cf74..338e99752e 100644 --- a/pkg/keystone/options/options.go +++ b/pkg/keystone/options/options.go @@ -36,7 +36,7 @@ type SKeystoneOptions struct { DefaultSyncIntervalSeconds int `help:"frequency to do auto sync tasks" default:"900"` - FetchProjectResourceCountIntervalSeconds int `help:"frequency tp fetch project resource counts" default:"900"` + FetchScopeResourceCountIntervalSeconds int `help:"frequency tp fetch project resource counts" default:"900"` PasswordExpirationSeconds int `help:"password expires after the duration in seconds"` PasswordMinimalLength int `help:"password minimal length" default:"6"` diff --git a/pkg/keystone/service/handlers.go b/pkg/keystone/service/handlers.go index 36c0863d65..19d7652525 100644 --- a/pkg/keystone/service/handlers.go +++ b/pkg/keystone/service/handlers.go @@ -63,7 +63,7 @@ func InitHandlers(app *appsrv.Application) { models.FernetKeyManager, - models.ProjectResourceManager, + models.ScopeResourceManager, db.SharedResourceManager, diff --git a/pkg/keystone/service/service.go b/pkg/keystone/service/service.go index 6d942a7e69..91556e31b2 100644 --- a/pkg/keystone/service/service.go +++ b/pkg/keystone/service/service.go @@ -81,7 +81,7 @@ func StartService() { cron := cronman.InitCronJobManager(true, opts.CronJobWorkerCount) cron.AddJobAtIntervalsWithStartRun("AutoSyncIdentityProviderTask", time.Duration(opts.AutoSyncIntervalSeconds)*time.Second, models.AutoSyncIdentityProviderTask, true) - cron.AddJobAtIntervalsWithStartRun("FetchProjectResourceCount", time.Duration(opts.FetchProjectResourceCountIntervalSeconds)*time.Second, cronjobs.FetchProjectResourceCount, false) + cron.AddJobAtIntervalsWithStartRun("FetchScopeResourceCount", time.Duration(opts.FetchScopeResourceCountIntervalSeconds)*time.Second, cronjobs.FetchScopeResourceCount, false) cron.AddJobAtIntervalsWithStartRun("CalculateIdentityQuotaUsages", time.Duration(opts.CalculateQuotaUsageIntervalSeconds)*time.Second, models.IdentityQuotaManager.CalculateQuotaUsages, true) cron.Start() diff --git a/pkg/mcclient/modulebase/projectresources.go b/pkg/mcclient/modulebase/scoperesources.go similarity index 83% rename from pkg/mcclient/modulebase/projectresources.go rename to pkg/mcclient/modulebase/scoperesources.go index a50ea29e0a..6076943531 100644 --- a/pkg/mcclient/modulebase/projectresources.go +++ b/pkg/mcclient/modulebase/scoperesources.go @@ -22,9 +22,9 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" ) -func GetProjectResources(s *mcclient.ClientSession, serviceType string) (jsonutils.JSONObject, error) { +func GetScopeResources(s *mcclient.ClientSession, serviceType string) (jsonutils.JSONObject, error) { man := &BaseManager{serviceType: serviceType} - resp, err := man.rawRequest(s, "GET", "/project-resources", nil, nil) + resp, err := man.rawRequest(s, "GET", "/scope-resources", nil, nil) if err != nil { return nil, err } diff --git a/pkg/mcclient/modules/managers.go b/pkg/mcclient/modules/managers.go index 71225afcb5..32f0a889d0 100644 --- a/pkg/mcclient/modules/managers.go +++ b/pkg/mcclient/modules/managers.go @@ -15,6 +15,7 @@ package modules import ( + "yunion.io/x/onecloud/pkg/apis" "yunion.io/x/onecloud/pkg/mcclient/modulebase" ) @@ -32,25 +33,25 @@ func NewResourceManager(serviceType string, keyword, keywordPlural string, func NewComputeManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("compute", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_REGION, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewActionManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("log", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_LOG, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewServiceTreeManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("servicetree", "", "v1", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_SERVICETREE, "", "v1", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewMonitorV2Manager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("monitor", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_MONITOR, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } @@ -63,13 +64,13 @@ func NewJointMonitorV2Manager(keyword, keywordPlural string, columns, adminColum func NewCloudwatcherManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("cloudwatcher", "", "v1", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_CLOUDWATCHER, "", "v1", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewNotifyManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("notify", "", "v1", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_NOTIFY, "", "v1", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } @@ -80,6 +81,13 @@ func NewJointComputeManager(keyword, keywordPlural string, columns, adminColumns Slave: slave} } +func NewJointCloudIdManager(keyword, keywordPlural string, columns, adminColumns []string, master, slave modulebase.Manager) modulebase.JointResourceManager { + return modulebase.JointResourceManager{ + ResourceManager: NewCloudIdManager(keyword, keywordPlural, columns, adminColumns), + Master: master, + Slave: slave} +} + func NewJointMonitorManager(keyword, keywordPlural string, columns, adminColumns []string, master, slave modulebase.Manager) modulebase.JointResourceManager { return modulebase.JointResourceManager{ ResourceManager: NewServiceTreeManager(keyword, keywordPlural, columns, adminColumns), @@ -89,96 +97,102 @@ func NewJointMonitorManager(keyword, keywordPlural string, columns, adminColumns func NewIdentityManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("identity", "", "v2.0", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_KEYSTONE, "", "v2.0", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewIdentityV3Manager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("identity", "", "v3", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_KEYSTONE, "", "v3", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewImageManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("image", "", "v1", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_IMAGE, "", "v1", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewVNCProxyManager() modulebase.ResourceManager { - return modulebase.ResourceManager{BaseManager: *modulebase.NewBaseManager("vncproxy", "", "", nil, nil), + return modulebase.ResourceManager{BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_VNCPROXY, "", "", nil, nil), Keyword: "vncproxy", KeywordPlural: "vncproxy"} } func NewITSMManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("itsm", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_ITSM, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewSchedulerManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("scheduler", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_SCHEDULER, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewMeterManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("meter", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_METER, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewYunionAgentManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("yunionagent", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_YUNIONAGENT, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewYunionConfManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("yunionconf", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_YUNIONCONF, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewAutoUpdateManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("autoupdate", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_AUTOUPDATE, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewWebsocketManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("websocket", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_WEBSOCKET, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } // deprecate func NewCloudmetaManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("cloudmeta", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_CLOUDMETA, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewOfflineCloudmetaManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("offlinecloudmeta", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_OFFLINE_CLOUDMETA, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewAnsibleManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("ansible", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_ANSIBLE, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewDevtoolManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("devtool", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_DEVTOOL, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } func NewCloudeventManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { return modulebase.ResourceManager{ - BaseManager: *modulebase.NewBaseManager("cloudevent", "", "", columns, adminColumns), + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_CLOUDEVENT, "", "", columns, adminColumns), + Keyword: keyword, KeywordPlural: keywordPlural} +} + +func NewCloudIdManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager { + return modulebase.ResourceManager{ + BaseManager: *modulebase.NewBaseManager(apis.SERVICE_TYPE_CLOUDID, "", "", columns, adminColumns), Keyword: keyword, KeywordPlural: keywordPlural} } diff --git a/pkg/mcclient/modules/mod_cloudgroup.go b/pkg/mcclient/modules/mod_cloudgroup.go new file mode 100644 index 0000000000..f40dc8d6cf --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudgroup.go @@ -0,0 +1,29 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + Cloudgroups modulebase.ResourceManager +) + +func init() { + Cloudgroups = NewCloudIdManager("cloudgroup", "cloudgroups", + []string{}, + []string{}) + + register(&Cloudgroups) +} diff --git a/pkg/mcclient/modules/mod_cloudgroupcaches.go b/pkg/mcclient/modules/mod_cloudgroupcaches.go new file mode 100644 index 0000000000..fcd4b1b758 --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudgroupcaches.go @@ -0,0 +1,29 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + Cloudgroupcaches modulebase.ResourceManager +) + +func init() { + Cloudgroupcaches = NewCloudIdManager("cloudgroupcache", "cloudgroupcaches", + []string{}, + []string{}) + + register(&Cloudgroupcaches) +} diff --git a/pkg/mcclient/modules/mod_cloudgrouppolicies.go b/pkg/mcclient/modules/mod_cloudgrouppolicies.go new file mode 100644 index 0000000000..7e4764e18c --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudgrouppolicies.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + Cloudgrouppolicies modulebase.JointResourceManager +) + +func init() { + Cloudgrouppolicies = NewJointCloudIdManager( + "cloudgroup_policy", + "cloudgroup_policies", + []string{"Cloudgroup_ID", "Cloudgroup", + "Cloudpolicy_ID", "Cloudpolicy", "Cloudaccount", "Cloudaccount_ID"}, + []string{}, + &Cloudgroups, + &Cloudpolicies) + register(&Cloudgrouppolicies) +} diff --git a/pkg/mcclient/modules/mod_cloudgroupusers.go b/pkg/mcclient/modules/mod_cloudgroupusers.go new file mode 100644 index 0000000000..b06d518af2 --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudgroupusers.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + Cloudgroupusers modulebase.JointResourceManager +) + +func init() { + Cloudgroupusers = NewJointCloudIdManager( + "cloudgroup_user", + "cloudgroup_users", + []string{"Cloudgroup_ID", "Cloudgroup", + "Clouduser_ID", "Clouduser", "Cloudaccount", "Cloudaccount_ID"}, + []string{}, + &Cloudgroups, + &Cloudusers) + register(&Cloudgroupusers) +} diff --git a/pkg/mcclient/modules/mod_cloudpolicy.go b/pkg/mcclient/modules/mod_cloudpolicy.go new file mode 100644 index 0000000000..f75bf4300f --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudpolicy.go @@ -0,0 +1,29 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + Cloudpolicies modulebase.ResourceManager +) + +func init() { + Cloudpolicies = NewCloudIdManager("cloudpolicy", "cloudpolicies", + []string{}, + []string{}) + + register(&Cloudpolicies) +} diff --git a/pkg/mcclient/modules/mod_clouduserpolicies.go b/pkg/mcclient/modules/mod_clouduserpolicies.go new file mode 100644 index 0000000000..c69f485f7b --- /dev/null +++ b/pkg/mcclient/modules/mod_clouduserpolicies.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +var ( + Clouduserpolicies modulebase.JointResourceManager +) + +func init() { + Clouduserpolicies = NewJointCloudIdManager( + "clouduser_policy", + "clouduser_policies", + []string{"Clouduser_ID", "Clouduser", + "Cloudpolicy_ID", "Cloudpolicy", "Cloudaccount", "Cloudaccount_ID", "Cloudprovider_ID", "Cloudprovider"}, + []string{}, + &Cloudusers, + &Cloudpolicies) + register(&Clouduserpolicies) +} diff --git a/pkg/mcclient/modules/mod_cloudusers.go b/pkg/mcclient/modules/mod_cloudusers.go new file mode 100644 index 0000000000..0c245c8b02 --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudusers.go @@ -0,0 +1,75 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type SClouduserManager struct { + modulebase.ResourceManager +} + +var ( + Cloudusers SClouduserManager +) + +func init() { + Cloudusers = SClouduserManager{NewCloudIdManager("clouduser", "cloudusers", + []string{}, + []string{})} + + register(&Cloudusers) +} + +func (this *SClouduserManager) GetLoginInfo(s *mcclient.ClientSession, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + data, err := this.Get(s, id, nil) + if err != nil { + return nil, err + } + + user := struct { + Id string + Name string + Secret string + IamLoginUrl string + }{} + + err = data.Unmarshal(&user) + if err != nil { + return nil, errors.Wrap(err, "data.Unmarshal") + } + + if len(user.Secret) == 0 { + return nil, httperrors.NewNotFoundError("No login secret found") + } + + password, err := utils.DescryptAESBase64(user.Id, user.Secret) + if err != nil { + return nil, errors.Wrap(err, "Descrypt") + } + + return jsonutils.Marshal(map[string]string{ + "username": user.Name, + "password": password, + "url": user.IamLoginUrl, + }), nil +} diff --git a/pkg/mcclient/modules/projectresources.go b/pkg/mcclient/modules/scoperesources.go similarity index 82% rename from pkg/mcclient/modules/projectresources.go rename to pkg/mcclient/modules/scoperesources.go index faa1069381..1996b5dc75 100644 --- a/pkg/mcclient/modules/projectresources.go +++ b/pkg/mcclient/modules/scoperesources.go @@ -21,6 +21,6 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/modulebase" ) -func GetProjectResources(s *mcclient.ClientSession, serviceType string) (jsonutils.JSONObject, error) { - return modulebase.GetProjectResources(s, serviceType) +func GetScopeResources(s *mcclient.ClientSession, serviceType string) (jsonutils.JSONObject, error) { + return modulebase.GetScopeResources(s, serviceType) } diff --git a/pkg/multicloud/aliyun/aliyun.go b/pkg/multicloud/aliyun/aliyun.go index cc67c191b6..8618a026df 100644 --- a/pkg/multicloud/aliyun/aliyun.go +++ b/pkg/multicloud/aliyun/aliyun.go @@ -53,6 +53,7 @@ const ( ALIYUN_RAM_API_VERSION = "2015-05-01" ALIYUN_API_VERION_RDS = "2014-08-15" ALIYUN_RM_API_VERSION = "2020-03-31" + ALIYUN_STS_API_VERSION = "2015-04-01" ) type AliyunClientConfig struct { diff --git a/pkg/multicloud/aliyun/cloudgroup.go b/pkg/multicloud/aliyun/cloudgroup.go new file mode 100644 index 0000000000..3e5f0caa9b --- /dev/null +++ b/pkg/multicloud/aliyun/cloudgroup.go @@ -0,0 +1,324 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aliyun + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SCloudgroup struct { + client *SAliyunClient + + Comments string + CreatedDate time.Time + GroupName string + UpdateDate time.Time +} + +func (group *SCloudgroup) GetName() string { + return group.GroupName +} + +func (group *SCloudgroup) GetDescription() string { + return group.Comments +} + +func (group *SCloudgroup) GetGlobalId() string { + return group.GroupName +} + +func (group *SCloudgroup) AddUser(name string) error { + return group.client.AddUserToGroup(group.GroupName, name) +} + +func (group *SCloudgroup) RemoveUser(name string) error { + return group.client.RemoveUserFromGroup(group.GroupName, name) +} + +func (group *SCloudgroup) AttachSystemPolicy(policyName string) error { + return group.client.AttachGroupPolicy(group.GroupName, policyName, "System") +} + +func (group *SCloudgroup) DetachSystemPolicy(policyName string) error { + return group.client.DetachPolicyFromGroup(group.GroupName, policyName, "System") +} + +func (group *SCloudgroup) Delete() error { + users, err := group.GetICloudusers() + if err != nil { + return errors.Wrap(err, "GetICloudusers") + } + for i := range users { + err = group.client.RemoveUserFromGroup(group.GroupName, users[i].GetName()) + if err != nil { + return errors.Wrapf(err, "RemoveUserFromGroup(%s)", users[i].GetName()) + } + } + policies, err := group.client.ListGroupPolicies(group.GroupName) + if err != nil { + return errors.Wrap(err, "GetICloudpolicies") + } + for i := range policies { + err = group.client.DetachPolicyFromGroup(group.GroupName, policies[i].GetName(), policies[i].PolicyType) + if err != nil { + return errors.Wrapf(err, "DetachPolicyFromGroup(%s)", policies[i].GetName()) + } + } + return group.client.DeleteGroup(group.GroupName) +} + +func (group *SCloudgroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users := []SClouduser{} + marker := "" + for { + part, err := group.client.ListGroupUsers(group.GroupName, marker, 100) + if err != nil { + return nil, errors.Wrap(err, "ListGroupUsers") + } + users = append(users, part.Users.User...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = group.client + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (group *SCloudgroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := group.client.ListGroupPolicies(group.GroupName) + if err != nil { + return nil, errors.Wrap(err, "ListGroupPolicie") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == "System" { + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +type Cloudgroups struct { + Group []SCloudgroup +} + +type SCloudgroups struct { + Groups Cloudgroups + Marker string + IsTruncated bool +} + +func (self *SAliyunClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups := []SCloudgroup{} + marker := "" + for { + part, err := self.GetCloudgroups(marker, 100) + if err != nil { + return nil, errors.Wrap(err, "GetCloudgroups") + } + groups = append(groups, part.Groups.Group...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = self + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (self *SAliyunClient) GetCloudgroups(marker string, maxItems int) (*SCloudgroups, error) { + params := map[string]string{} + if len(marker) > 0 { + params["Marker"] = marker + } + if maxItems > 0 { + params["MaxItems"] = fmt.Sprintf("%d", maxItems) + } + groups := SCloudgroups{} + resp, err := self.ramRequest("ListGroups", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListGroups") + } + err = resp.Unmarshal(&groups) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return &groups, nil +} + +// https://help.aliyun.com/document_detail/28732.html?spm=a2c4g.11186623.6.777.580735b2m2xUh8 +func (self *SAliyunClient) ListGroupPolicies(groupName string) ([]SPolicy, error) { + params := map[string]string{ + "GroupName": groupName, + } + resp, err := self.ramRequest("ListPoliciesForGroup", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListPoliciesForGroup") + } + policies := []SPolicy{} + err = resp.Unmarshal(&policies, "Policies", "Policy") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return policies, nil +} + +type Cloudusers struct { + User []SClouduser +} + +type SCloudusers struct { + Users Cloudusers + Marker string + IsTruncated bool +} + +func (self *SAliyunClient) ListGroupUsers(groupName string, marker string, maxItems int) (*SCloudusers, error) { + params := map[string]string{ + "GroupName": groupName, + } + if len(marker) > 0 { + params["Marker"] = marker + } + if maxItems > 0 { + params["MaxItems"] = fmt.Sprintf("%d", maxItems) + } + resp, err := self.ramRequest("ListUsersForGroup", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.ListUserForGroup") + } + users := &SCloudusers{} + err = resp.Unmarshal(users) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return users, nil +} + +func (self *SAliyunClient) DeleteGroup(groupName string) error { + params := map[string]string{ + "GroupName": groupName, + } + _, err := self.ramRequest("DeleteGroup", params) + return err +} + +func (self *SAliyunClient) CreateGroup(groupName, comments string) (*SCloudgroup, error) { + params := map[string]string{ + "GroupName": groupName, + } + if len(comments) > 0 { + params["Comments"] = comments + } + resp, err := self.ramRequest("CreateGroup", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateGroup") + } + group := &SCloudgroup{client: self} + err = resp.Unmarshal(group, "Group") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return group, nil +} + +func (self *SAliyunClient) GetGroup(groupName string) (*SCloudgroup, error) { + params := map[string]string{ + "GroupName": groupName, + } + resp, err := self.ramRequest("GetGroup", params) + if err != nil { + return nil, errors.Wrap(err, "GetGroup") + } + group := &SCloudgroup{client: self} + err = resp.Unmarshal(group, "Group") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return group, nil +} + +func (self *SAliyunClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.GetGroup(name) +} + +func (self *SAliyunClient) RemoveUserFromGroup(groupName, userName string) error { + params := map[string]string{ + "GroupName": groupName, + "UserName": userName, + } + _, err := self.ramRequest("RemoveUserFromGroup", params) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "RemoveUserFromGroup") + } + return nil +} + +func (self *SAliyunClient) DetachPolicyFromGroup(groupName, policyName, policyType string) error { + params := map[string]string{ + "GroupName": groupName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("DetachPolicyFromGroup", params) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "DetachPolicyFromGroup") + } + return nil +} + +func (self *SAliyunClient) CreateICloudgroup(name string, desc string) (cloudprovider.ICloudgroup, error) { + return self.CreateGroup(name, desc) +} + +func (self *SAliyunClient) AddUserToGroup(groupName, userName string) error { + params := map[string]string{ + "GroupName": groupName, + "UserName": userName, + } + _, err := self.ramRequest("AddUserToGroup", params) + if err != nil && !strings.Contains(err.Error(), "EntityAlreadyExists.User.Group") { + return errors.Wrap(err, "AddUserToGroup") + } + return nil +} + +func (self *SAliyunClient) AttachGroupPolicy(groupName, policyName, policyType string) error { + params := map[string]string{ + "GroupName": groupName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("AttachPolicyToGroup", params) + if err != nil && !strings.Contains(err.Error(), "EntityAlreadyExists.Group.Policy") { + return errors.Wrap(err, "AttachPolicyToGroup") + } + return nil +} diff --git a/pkg/multicloud/aliyun/clouduser.go b/pkg/multicloud/aliyun/clouduser.go new file mode 100644 index 0000000000..49fd9db6a3 --- /dev/null +++ b/pkg/multicloud/aliyun/clouduser.go @@ -0,0 +1,366 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aliyun + +import ( + "fmt" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SClouduser struct { + client *SAliyunClient + + Comments string + CreateDate time.Time + DisplayName string + Email string + MobilePhone string + UserId string + UserName string +} + +func (user *SClouduser) GetGlobalId() string { + if len(user.UserId) > 0 { + return user.UserId + } + u, err := user.client.GetClouduser(user.UserName) + if err != nil { + return "" + } + return u.UserId +} + +func (user *SClouduser) GetName() string { + return user.UserName +} + +func (user *SClouduser) Delete() error { + groups, err := user.client.ListGroupsForUser(user.UserName) + if err != nil { + return errors.Wrap(err, "ListGroupsForUser") + } + for i := range groups { + err = user.client.RemoveUserFromGroup(groups[i].GroupName, user.UserName) + if err != nil { + return errors.Wrapf(err, "RemoveUserFromGroup %s > %s", groups[i].GroupName, user.UserName) + } + } + policies, err := user.client.ListPoliciesForUser(user.UserName) + if err != nil { + return errors.Wrap(err, "ListPoliciesForUser") + } + for i := range policies { + err = user.client.DetachPolicyFromUser(policies[i].PolicyName, policies[i].PolicyType, user.UserName) + if err != nil { + return errors.Wrapf(err, "DetachPolicyFromUser %s %s %s", policies[i].PolicyName, policies[i].PolicyType, user.UserName) + } + } + return user.client.DeleteClouduser(user.UserName) +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := user.client.ListGroupsForUser(user.UserName) + if err != nil { + return nil, errors.Wrap(err, "ListGroupsForUser") + } + iGroups := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = user.client + iGroups = append(iGroups, &groups[i]) + } + return iGroups, nil +} + +func (user *SClouduser) UpdatePassword(password string) error { + return user.client.UpdateLoginProfile(user.UserName, password) +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := user.client.ListPoliciesForUser(user.UserName) + if err != nil { + return nil, errors.Wrap(err, "ListPoliciesForUser") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == "System" { + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (user *SClouduser) IsConsoleLogin() bool { + _, err := user.client.GetLoginProfile(user.UserName) + if errors.Cause(err) == cloudprovider.ErrNotFound { + return false + } + return true +} + +func (user *SClouduser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.UserName, password) +} + +func (user *SClouduser) AttachSystemPolicy(policyName string) error { + return user.client.AttachPolicyToUser(policyName, "System", user.UserName) +} + +func (user *SClouduser) DetachSystemPolicy(policyName string) error { + return user.client.DetachPolicyFromUser(policyName, "System", user.UserName) +} + +func (self *SAliyunClient) DeleteClouduser(name string) error { + params := map[string]string{ + "UserName": name, + } + _, err := self.ramRequest("DeleteUser", params) + return err +} + +func (self *SAliyunClient) CreateClouduser(name, phone, email, comments string) (*SClouduser, error) { + params := map[string]string{ + "UserName": name, + "DisplayName": name, + } + if len(phone) > 0 { + params["MobilePhone"] = phone + } + if len(email) > 0 { + params["Email"] = email + } + if len(comments) > 0 { + params["Comments"] = comments + } + resp, err := self.ramRequest("CreateUser", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateUser") + } + + user := &SClouduser{client: self} + err = resp.Unmarshal(user, "User") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return user, nil +} + +func (self *SAliyunClient) GetCloudusers(marker string, maxItems int) ([]SClouduser, string, error) { + params := map[string]string{} + if len(marker) > 0 { + params["Marker"] = marker + } + if maxItems > 0 { + params["MaxItems"] = fmt.Sprintf("%d", maxItems) + } + resp, err := self.ramRequest("ListUsers", params) + if err != nil { + return nil, "", errors.Wrap(err, "ramRequest.ListUsers") + } + users := []SClouduser{} + err = resp.Unmarshal(&users, "Users", "User") + if err != nil { + return nil, "", errors.Wrap(err, "resp.Unmarshal") + } + marker, _ = resp.GetString("Marker") + return users, marker, nil +} + +func (self *SAliyunClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + user, err := self.CreateClouduser(conf.Name, conf.MobilePhone, conf.Email, conf.Desc) + if err != nil { + return nil, errors.Wrap(err, "CreateClouduser") + } + if len(conf.Password) > 0 { + _, err := self.CreateLoginProfile(conf.Name, conf.Password) + if err != nil { + return nil, errors.Wrap(err, "CreateLoginProfile") + } + } + for _, policyId := range conf.ExternalPolicyIds { + err := user.AttachSystemPolicy(policyId) + if err != nil { + log.Errorf("attach policy %s for user %s error: %v", policyId, conf.Name, err) + } + } + return user, nil +} + +func (self *SAliyunClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + var ( + ret []cloudprovider.IClouduser + users []SClouduser + marker string + err error + ) + for { + users, marker, err = self.GetCloudusers(marker, 100) + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + for i := range users { + users[i].client = self + ret = append(ret, &users[i]) + } + if len(marker) == 0 || len(users) == 0 { + break + } + } + return ret, nil +} + +func (self *SAliyunClient) GetClouduser(name string) (*SClouduser, error) { + params := map[string]string{ + "UserName": name, + } + resp, err := self.ramRequest("GetUser", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateUser") + } + user := &SClouduser{client: self} + err = resp.Unmarshal(user, "User") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return user, nil +} + +func (self *SAliyunClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.GetClouduser(name) +} + +type SLoginProfile struct { + CreateDate string + MFABindRequired bool + PasswordResetRequired bool + UserName string +} + +func (self *SAliyunClient) GetLoginProfile(name string) (*SLoginProfile, error) { + params := map[string]string{ + "UserName": name, + } + resp, err := self.ramRequest("GetLoginProfile", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.GetLoginProfile") + } + profile := &SLoginProfile{} + err = resp.Unmarshal(profile, "LoginProfile") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return profile, nil +} + +func (self *SAliyunClient) DeleteLoginProfile(name string) error { + params := map[string]string{ + "UserName": name, + } + _, err := self.ramRequest("DeleteLoginProfile", params) + return err +} + +func (self *SAliyunClient) CreateLoginProfile(name, password string) (*SLoginProfile, error) { + params := map[string]string{ + "UserName": name, + "Password": password, + } + resp, err := self.ramRequest("CreateLoginProfile", params) + if err != nil { + return nil, errors.Wrap(err, "ramRequest.CreateLoginProfile") + } + profile := &SLoginProfile{} + err = resp.Unmarshal(profile, "LoginProfile") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return profile, nil +} + +func (self *SAliyunClient) UpdateLoginProfile(name, password string) error { + params := map[string]string{ + "UserName": name, + "Password": password, + } + _, err := self.ramRequest("UpdateLoginProfile", params) + if err != nil { + return errors.Wrap(err, "ramRequest.CreateLoginProfile") + } + return nil +} + +func (self *SAliyunClient) ResetClouduserPassword(name, password string) error { + _, err := self.GetLoginProfile(name) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + _, err = self.CreateLoginProfile(name, password) + return err + } + return errors.Wrap(err, "GetLoginProfile") + } + return self.UpdateLoginProfile(name, password) +} + +func (self *SAliyunClient) GetIamLoginUrl() string { + params := map[string]string{} + resp, err := self.ramRequest("GetAccountAlias", params) + if err != nil { + log.Errorf("GetAccountAlias error: %v", err) + return "" + } + alias, _ := resp.GetString("AccountAlias") + if len(alias) > 0 { + return fmt.Sprintf("https://signin.aliyun.com/%s.onaliyun.com/login.htm", alias) + } + return "" +} + +// https://help.aliyun.com/document_detail/28707.html?spm=a2c4g.11186623.6.752.f4466bbfVy5j0s +func (self *SAliyunClient) ListGroupsForUser(user string) ([]SCloudgroup, error) { + params := map[string]string{ + "UserName": user, + } + resp, err := self.ramRequest("ListGroupsForUser", params) + if err != nil { + return nil, errors.Wrap(err, "ListGroupsForUser") + } + groups := []SCloudgroup{} + err = resp.Unmarshal(&groups, "Groups", "Group") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return groups, nil +} + +// https://help.aliyun.com/document_detail/28732.html?spm=a2c4g.11186623.6.777.580735b2m2xUh8 +func (self *SAliyunClient) ListPoliciesForUser(user string) ([]SPolicy, error) { + params := map[string]string{ + "UserName": user, + } + resp, err := self.ramRequest("ListPoliciesForUser", params) + if err != nil { + return nil, errors.Wrap(err, "ListPoliciesForUser") + } + policies := []SPolicy{} + err = resp.Unmarshal(&policies, "Policies", "Policy") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return policies, nil +} diff --git a/pkg/multicloud/aliyun/provider/provider.go b/pkg/multicloud/aliyun/provider/provider.go index 4c70b48c3b..edfc10b172 100644 --- a/pkg/multicloud/aliyun/provider/provider.go +++ b/pkg/multicloud/aliyun/provider/provider.go @@ -43,6 +43,14 @@ func (self *SAliyunProviderFactory) IsCloudeventRegional() bool { return true } +func (self *SAliyunProviderFactory) IsSupportClouduser() bool { + return true +} + +func (self *SAliyunProviderFactory) IsSupportCreateCloudgroup() bool { + return true +} + func (self *SAliyunProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { output := cloudprovider.SCloudaccount{} if len(input.AccessKeyId) == 0 { @@ -164,3 +172,39 @@ func (self *SAliyunProvider) GetStorageClasses(regionId string) []string { func (self *SAliyunProvider) GetCapabilities() []string { return self.client.GetCapabilities() } + +func (self *SAliyunProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SAliyunProvider) IsSupportCloudId() bool { + return cloudprovider.IsSupportCloudId(self) +} + +func (self *SAliyunProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SAliyunProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SAliyunProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SAliyunProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} + +func (self *SAliyunProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} + +func (self *SAliyunProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SAliyunProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetISystemCloudpolicies() +} diff --git a/pkg/multicloud/aliyun/ram.go b/pkg/multicloud/aliyun/ram.go index 608bb57f5b..ef3f2394af 100644 --- a/pkg/multicloud/aliyun/ram.go +++ b/pkg/multicloud/aliyun/ram.go @@ -15,10 +15,12 @@ package aliyun import ( + "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -123,6 +125,76 @@ type SPolicy struct { PolicyType string } +func (policy *SPolicy) GetName() string { + return policy.PolicyName +} + +func (policy *SPolicy) GetPolicyType() string { + return policy.PolicyType +} + +func (policy *SPolicy) GetDescription() string { + return policy.Description +} + +func (policy *SPolicy) GetGlobalId() string { + return policy.PolicyName +} + +func (self *SAliyunClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := self.ListPolicies("System", "") + if err != nil { + return nil, errors.Wrap(err, "GetPolicy") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + ret = append(ret, &policies[i]) + } + return ret, nil +} + +func (self *SAliyunClient) GetICloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies, err := self.ListPolicies("", "") + if err != nil { + return nil, errors.Wrap(err, "GetPolicy") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + ret = append(ret, &policies[i]) + } + return ret, nil +} + +func (self *SAliyunClient) AttachPolicyToUser(policyName, policyType, userName string) error { + params := map[string]string{ + "UserName": userName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("AttachPolicyToUser", params) + if err != nil && !strings.Contains(err.Error(), "EntityAlreadyExists.User.Policy") { + return errors.Wrap(err, "AttachPolicyToUser") + } + return nil +} + +func (self *SAliyunClient) DetachPolicyFromUser(policyName, policyType, userName string) error { + if len(policyType) == 0 { + policyType = "System" + } + params := map[string]string{ + "UserName": userName, + "PolicyName": policyName, + "PolicyType": policyType, + } + _, err := self.ramRequest("DetachPolicyFromUser", params) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "DetachPolicyFromUser") + } + return nil +} + +// https://help.aliyun.com/document_detail/28719.html?spm=a2c4g.11174283.6.764.27055662H6TGg5 func (self *SAliyunClient) ListPolicies(policyType string, role string) ([]SPolicy, error) { var action string params := make(map[string]string) diff --git a/pkg/multicloud/aliyun/shell/cloudgroup.go b/pkg/multicloud/aliyun/shell/cloudgroup.go new file mode 100644 index 0000000000..4236dd14cb --- /dev/null +++ b/pkg/multicloud/aliyun/shell/cloudgroup.go @@ -0,0 +1,109 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CloudgroupCreateOptions struct { + NAME string + Comments string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create Cloud group", func(cli *aliyun.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Comments) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupListOptions struct { + Marker string + MaxItems int + } + + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List Cloud groups", func(cli *aliyun.SRegion, args *CloudgroupListOptions) error { + groups, err := cli.GetClient().GetCloudgroups(args.Marker, args.MaxItems) + if err != nil { + return err + } + printList(groups.Groups.Group, 0, 0, 0, nil) + return nil + }) + + type CloudgroupDeleteOptions struct { + NAME string + } + + shellutils.R(&CloudgroupDeleteOptions{}, "cloud-group-delete", "Delete Cloud group", func(cli *aliyun.SRegion, args *CloudgroupDeleteOptions) error { + return cli.GetClient().DeleteGroup(args.NAME) + }) + + type GroupExtListOptions struct { + GROUP string + Marker string + MaxItems int + } + shellutils.R(&GroupExtListOptions{}, "cloud-group-user-list", "List Cloud group users", func(cli *aliyun.SRegion, args *GroupExtListOptions) error { + users, err := cli.GetClient().ListGroupUsers(args.GROUP, args.Marker, args.MaxItems) + if err != nil { + return err + } + printList(users.Users.User, 0, 0, 0, nil) + return nil + }) + + type GroupUserOptions struct { + GROUP string + USER string + } + + shellutils.R(&GroupUserOptions{}, "cloud-group-remove-user", "Remove user from group", func(cli *aliyun.SRegion, args *GroupUserOptions) error { + return cli.GetClient().RemoveUserFromGroup(args.GROUP, args.USER) + }) + + shellutils.R(&GroupUserOptions{}, "cloud-group-add-user", "Add user to group", func(cli *aliyun.SRegion, args *GroupUserOptions) error { + return cli.GetClient().AddUserToGroup(args.GROUP, args.USER) + }) + + shellutils.R(&GroupExtListOptions{}, "cloud-group-policy-list", "List Cloud group policies", func(cli *aliyun.SRegion, args *GroupExtListOptions) error { + policies, err := cli.GetClient().ListGroupPolicies(args.GROUP) + if err != nil { + return err + } + printList(policies, 0, 0, 0, nil) + return nil + }) + + type GroupPolicyOptions struct { + GROUP string + PolicyType string `default:"System" choices:"System|Custom"` + POLICY string + } + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-attach-policy", "Attach policy for group", func(cli *aliyun.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().AttachGroupPolicy(args.GROUP, args.POLICY, args.PolicyType) + }) + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-detach-policy", "Detach policy from group", func(cli *aliyun.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().DetachPolicyFromGroup(args.GROUP, args.POLICY, args.PolicyType) + }) + +} diff --git a/pkg/multicloud/aliyun/shell/clouduser.go b/pkg/multicloud/aliyun/shell/clouduser.go new file mode 100644 index 0000000000..eba9dffe32 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/clouduser.go @@ -0,0 +1,106 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserCreateOptions struct { + NAME string + MobilePhone string + Comments string + Email string + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create Cloud user", func(cli *aliyun.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateClouduser(args.NAME, args.MobilePhone, args.Email, args.Comments) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type ClouduserListOptions struct { + Marker string + MaxItems int + } + + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List Cloud users", func(cli *aliyun.SRegion, args *ClouduserListOptions) error { + users, _, err := cli.GetClient().GetCloudusers(args.Marker, args.MaxItems) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type ClouduserOptions struct { + NAME string + } + + shellutils.R(&ClouduserOptions{}, "cloud-user-delete", "Delete Cloud user", func(cli *aliyun.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteClouduser(args.NAME) + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-loginprofile", "Get Cloud user loginprofile", func(cli *aliyun.SRegion, args *ClouduserOptions) error { + profile, err := cli.GetClient().GetLoginProfile(args.NAME) + if err != nil { + return err + } + printObject(profile) + return nil + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-delete-loginprofile", "Delete Cloud user loginprofile", func(cli *aliyun.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteLoginProfile(args.NAME) + }) + + type LoginProfileCreateOptions struct { + NAME string + PASSWORD string + } + + shellutils.R(&LoginProfileCreateOptions{}, "cloud-user-create-loginprofile", "Create Cloud user loginprofile", func(cli *aliyun.SRegion, args *LoginProfileCreateOptions) error { + profile, err := cli.GetClient().CreateLoginProfile(args.NAME, args.PASSWORD) + if err != nil { + return err + } + printObject(profile) + return nil + }) + + shellutils.R(&LoginProfileCreateOptions{}, "cloud-user-reset-password", "Reset Cloud user password", func(cli *aliyun.SRegion, args *LoginProfileCreateOptions) error { + return cli.GetClient().ResetClouduserPassword(args.NAME, args.PASSWORD) + }) + + type ClouduserPolicyOptions struct { + PolicyType string `default:"System" choices:"System|Custom"` + POLICY string + USER string + } + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-attach-policy", "Attach policy for user", func(cli *aliyun.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().AttachPolicyToUser(args.POLICY, args.PolicyType, args.USER) + }) + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-detach-policy", "Detach policy from user", func(cli *aliyun.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().DetachPolicyFromUser(args.POLICY, args.PolicyType, args.USER) + }) + +} diff --git a/pkg/multicloud/aliyun/shell/ram.go b/pkg/multicloud/aliyun/shell/ram.go index 64db9817e0..45f8995c39 100644 --- a/pkg/multicloud/aliyun/shell/ram.go +++ b/pkg/multicloud/aliyun/shell/ram.go @@ -91,4 +91,17 @@ func init() { shellutils.R(&ListRolesOptions{}, "enable-image-export", "Enable image export privilege", func(cli *aliyun.SRegion, args *ListRolesOptions) error { return cli.GetClient().EnableImageExport() }) + + type CallerShowOptions struct { + } + + shellutils.R(&CallerShowOptions{}, "caller-show", "Show caller info", func(cli *aliyun.SRegion, args *CallerShowOptions) error { + caller, err := cli.GetClient().GetCallerIdentity() + if err != nil { + return err + } + printObject(caller) + return nil + }) + } diff --git a/pkg/multicloud/aliyun/sts.go b/pkg/multicloud/aliyun/sts.go new file mode 100644 index 0000000000..b5c3f5e956 --- /dev/null +++ b/pkg/multicloud/aliyun/sts.go @@ -0,0 +1,46 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aliyun + +import ( + "yunion.io/x/jsonutils" +) + +func (self *SAliyunClient) stsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := self.getDefaultClient() + if err != nil { + return nil, err + } + return jsonRequest(cli, "sts.aliyuncs.com", ALIYUN_STS_API_VERSION, apiName, params, self.debug) +} + +type SCallerIdentity struct { + Arn string + AccountId string + UserId string + RoleId string + PrincipalId string + IdentityType string +} + +func (self *SAliyunClient) GetCallerIdentity() (*SCallerIdentity, error) { + params := map[string]string{} + resp, err := self.stsRequest("GetCallerIdentity", params) + if err != nil { + return nil, err + } + id := &SCallerIdentity{} + return id, resp.Unmarshal(id) +} diff --git a/pkg/multicloud/aws/aws.go b/pkg/multicloud/aws/aws.go index 13d05148a7..99afd6551e 100644 --- a/pkg/multicloud/aws/aws.go +++ b/pkg/multicloud/aws/aws.go @@ -15,12 +15,19 @@ package aws import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" sdk "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/client/metadata" + "github.com/aws/aws-sdk-go/aws/corehandlers" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" + v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + "github.com/aws/aws-sdk-go/private/protocol/query" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/s3" @@ -74,6 +81,7 @@ func (cfg *AwsClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConf func (cfg *AwsClientConfig) Debug(debug bool) *AwsClientConfig { cfg.debug = debug + DEBUG = debug return cfg } @@ -396,6 +404,45 @@ func (self *SAwsClient) GetAccessEnv() string { } } +func (self *SAwsClient) request(serviceName, serviceId, apiVersion string, apiName string, params map[string]string, retval interface{}) error { + session, err := self.getAwsSession("") + if err != nil { + return err + } + c := session.ClientConfig(serviceName) + metadata := metadata.ClientInfo{ + ServiceName: serviceName, + ServiceID: serviceId, + SigningName: c.SigningName, + SigningRegion: c.SigningRegion, + Endpoint: c.Endpoint, + APIVersion: apiVersion, + } + + if self.debug { + logLevel := aws.LogLevelType(uint(aws.LogDebugWithRequestErrors) + uint(aws.LogDebugWithHTTPBody)) + c.Config.LogLevel = &logLevel + } + + client := client.New(*c.Config, metadata, c.Handlers) + client.Handlers.Sign.PushBackNamed(v4.SignRequestHandler) + client.Handlers.Build.PushBackNamed(buildHandler) + client.Handlers.Unmarshal.PushBackNamed(UnmarshalHandler) + client.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler) + client.Handlers.UnmarshalError.PushBackNamed(query.UnmarshalErrorHandler) + client.Handlers.Validate.Remove(corehandlers.ValidateEndpointHandler) + return jsonRequest(client, apiName, params, retval, true) + +} + +func (self *SAwsClient) iamRequest(apiName string, params map[string]string, retval interface{}) error { + return self.request(IAM_SERVICE_NAME, IAM_SERVICE_ID, "2010-05-08", apiName, params, retval) +} + +func (self *SAwsClient) stsRequest(apiName string, params map[string]string, retval interface{}) error { + return self.request(STS_SERVICE_NAME, STS_SERVICE_ID, "2011-06-15", apiName, params, retval) +} + func jsonRequest(cli *client.Client, apiName string, params map[string]string, retval interface{}, debug bool) error { op := &request.Operation{ Name: apiName, @@ -412,6 +459,9 @@ func jsonRequest(cli *client.Client, apiName string, params map[string]string, r req := cli.NewRequest(op, params, retval) err := req.Send() if err != nil { + if e, ok := err.(awserr.RequestFailure); ok && e.StatusCode() == 404 { + return cloudprovider.ErrNotFound + } return err } return nil @@ -445,3 +495,18 @@ func cloudWatchRequest(cli *client.Client, apiName string, params *cloudwatch.Ge } return nil } + +func (client *SAwsClient) GetIamLoginUrl() string { + identity, err := client.GetCallerIdentity() + if err != nil { + log.Errorf("failed to get caller identity error: %v", err) + return "" + } + + switch client.accessUrl { + case "ChinaCloud": + return fmt.Sprintf("https://%s.signin.amazonaws.cn/console/", identity.Account) + default: + return fmt.Sprintf("https://%s.signin.aws.amazon.com/console/", identity.Account) + } +} diff --git a/pkg/multicloud/aws/cloudgroup.go b/pkg/multicloud/aws/cloudgroup.go new file mode 100644 index 0000000000..f160d57b06 --- /dev/null +++ b/pkg/multicloud/aws/cloudgroup.go @@ -0,0 +1,283 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "fmt" + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SCloudgroup struct { + client *SAwsClient + + Path string `xml:"Path"` + GroupName string `xml:"GroupName"` + GroupId string `xml:"GroupId"` + Arn string `xml:"Arn"` + CreateDate time.Time `xml:"CreateDate"` +} + +func (self *SCloudgroup) GetGlobalId() string { + return self.GroupId +} + +func (self *SCloudgroup) GetName() string { + return self.GroupName +} + +func (self *SCloudgroup) GetDescription() string { + return self.Path +} + +func (self *SCloudgroup) Delete() error { + return self.client.DeleteGroup(self.GroupName) +} + +func (self *SCloudgroup) AddUser(name string) error { + return self.client.AddUserToGroup(self.GroupName, name) +} + +func (self *SCloudgroup) RemoveUser(name string) error { + return self.client.RemoveUserFromGroup(self.GroupName, name) +} + +func (self *SCloudgroup) AttachSystemPolicy(policyArn string) error { + return self.client.AttachGroupPolicy(self.GroupName, policyArn) +} + +func (self *SCloudgroup) DetachSystemPolicy(policyArn string) error { + return self.client.DetachGroupPolicy(self.GroupName, policyArn) +} + +func (self *SCloudgroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users := []SClouduser{} + marker := "" + for { + part, err := self.client.GetGroup(self.GroupName, marker, 1000) + if err != nil { + return nil, errors.Wrap(err, "GetGroup") + } + users = append(users, part.Users...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = self.client + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (self *SCloudgroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SPolicy{} + marker := "" + for { + part, err := self.client.ListGroupPolicies(self.GroupName, marker, 1000) + if err != nil { + return nil, errors.Wrap(err, "ListGroupPolicies") + } + policies = append(policies, part.Policies...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + ret = append(ret, &policies[i]) + } + return ret, nil +} + +type SCloudgroups struct { + Groups []SCloudgroup `xml:"Groups>member"` + IsTruncated bool `xml:"IsTruncated"` + Marker string `xml:"Marker"` +} + +func (self *SAwsClient) CreateGroup(name string, path string) (*SCloudgroup, error) { + params := map[string]string{ + "Path": "/", + "GroupName": name, + } + if len(path) > 0 { + params["Path"] = path + } + group := struct { + Group SCloudgroup `xml:"Group"` + }{} + err := self.iamRequest("CreateGroup", params, &group) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.CreateGroup") + } + group.Group.client = self + return &group.Group, nil +} + +func (self *SAwsClient) ListGroups(marker string, maxItems int, pathPrefix string) (*SCloudgroups, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + params := map[string]string{ + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + if len(pathPrefix) > 0 { + params["PathPrefix"] = pathPrefix + } + groups := &SCloudgroups{} + err := self.iamRequest("ListGroups", params, groups) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.ListGroups") + } + return groups, nil +} + +func (self *SAwsClient) DeleteGroup(name string) error { + params := map[string]string{ + "GroupName": name, + } + return self.iamRequest("ListGroups", params, nil) +} + +func (self *SAwsClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups := []SCloudgroup{} + marker := "" + for { + part, err := self.ListGroups(marker, 1000, "") + if err != nil { + return nil, errors.Wrap(err, "ListGroups") + } + groups = append(groups, part.Groups...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = self + ret = append(ret, &groups[i]) + } + return ret, nil +} + +type SCloudgroupDetails struct { + Group SCloudgroup `xml:"Group"` + Users []SClouduser `xml:"Users>member"` + IsTruncated bool `xml:"IsTruncated"` + Marker string `xml:"Marker"` +} + +func (self *SAwsClient) GetGroup(name string, marker string, maxItems int) (*SCloudgroupDetails, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + params := map[string]string{ + "GroupName": name, + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + group := &SCloudgroupDetails{} + err := self.iamRequest("GetGroup", params, group) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.GetGroup") + } + return group, nil +} + +func (self *SAwsClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + group, err := self.GetGroup(name, "", 1) + if err != nil { + return nil, err + } + group.Group.client = self + return &group.Group, nil +} + +func (self *SAwsClient) ListGroupPolicies(name string, marker string, maxItems int) (*SPolicies, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + + params := map[string]string{ + "GroupName": name, + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + policies := &SPolicies{} + err := self.iamRequest("ListGroupPolicies", params, policies) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.ListGroupPolicies") + } + return policies, nil +} + +func (self *SAwsClient) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + group, err := self.CreateGroup(name, "") + if err != nil { + return nil, errors.Wrap(err, "CreateGroup") + } + return group, nil +} + +func (self *SAwsClient) AddUserToGroup(groupName, userName string) error { + params := map[string]string{ + "GroupName": groupName, + "UserName": userName, + } + return self.iamRequest("AddUserToGroup", params, nil) +} + +func (self *SAwsClient) RemoveUserFromGroup(groupName, userName string) error { + params := map[string]string{ + "GroupName": groupName, + "UserName": userName, + } + return self.iamRequest("RemoveUserFromGroup", params, nil) +} + +func (self *SAwsClient) AttachGroupPolicy(groupName, policyArn string) error { + params := map[string]string{ + "GroupName": groupName, + "PolicyArn": policyArn, + } + return self.iamRequest("AttachGroupPolicy", params, nil) +} + +func (self *SAwsClient) DetachGroupPolicy(groupName, policyArn string) error { + params := map[string]string{ + "GroupName": groupName, + "PolicyArn": policyArn, + } + err := self.iamRequest("DetachGroupPolicy", params, nil) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "DetachGroupPolicy") + } + return nil +} diff --git a/pkg/multicloud/aws/clouduser.go b/pkg/multicloud/aws/clouduser.go new file mode 100644 index 0000000000..26bd34d8d1 --- /dev/null +++ b/pkg/multicloud/aws/clouduser.go @@ -0,0 +1,506 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import ( + "fmt" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SClouduser struct { + client *SAwsClient + UserId string `xml:"UserId"` + Path string `xml:"Path"` + UserName string `xml:"UserName"` + Arn string `xml:"Arn"` + CreateDate time.Time `xml:"CreateDate"` + PasswordLastUsed time.Time `xml:"PasswordLastUsed"` +} + +func (user *SClouduser) AttachSystemPolicy(policyArn string) error { + return user.client.AttachPolicy(user.UserName, policyArn) +} + +func (user *SClouduser) DetachSystemPolicy(policyId string) error { + return user.client.DetachPolicy(user.UserName, policyId) +} + +func (user *SClouduser) GetGlobalId() string { + return user.UserId +} + +func (user *SClouduser) GetName() string { + return user.UserName +} + +func (user *SClouduser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.UserName, password) +} + +func (user *SClouduser) IsConsoleLogin() bool { + _, err := user.client.GetLoginProfile(user.UserName) + if err == cloudprovider.ErrNotFound { + return false + } + return true +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups := []SCloudgroup{} + marker := "" + for { + part, err := user.client.ListGroupsForUser(user.UserName, marker, 1000) + if err != nil { + return nil, errors.Wrap(err, "ListGroupsForUser") + } + groups = append(groups, part.Groups...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = user.client + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (self *SAwsClient) GetICloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SPolicy{} + marker := "" + for { + part, err := self.GetPolicies(marker, 1000, false, "", "", "") + if err != nil { + return nil, errors.Wrap(err, "GetPolicies") + } + policies = append(policies, part.Policies...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + ret = append(ret, &policies[i]) + } + return ret, nil +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SAttachdPolicy{} + marker := "" + for { + part, err := user.client.GetAttachedPolicies(user.UserName, marker, 1000, "") + if err != nil { + return nil, errors.Wrap(err, "GetAttachedPolicies") + } + policies = append(policies, part.AttachedPolicies...) + marker = part.Marker + if len(marker) == 0 { + break + } + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + policies[i].client = user.client + ret = append(ret, &policies[i]) + } + return ret, nil +} + +func (user *SClouduser) Delete() error { + marker := "" + for { + groups, err := user.client.ListGroupsForUser(user.UserName, marker, 1000) + if err != nil { + return errors.Wrap(err, "ListGroupsForUser") + } + for _, group := range groups.Groups { + err = user.client.RemoveUserFromGroup(group.GroupName, user.UserName) + if err != nil { + return errors.Wrap(err, "RemoveUserFromGroup") + } + } + marker = groups.Marker + if len(marker) == 0 { + break + } + } + policies, err := user.GetISystemCloudpolicies() + if err != nil { + return errors.Wrap(err, "GetICloudpolicies") + } + for i := range policies { + err = user.DetachSystemPolicy(policies[i].GetGlobalId()) + if err != nil { + return errors.Wrap(err, "DetachPolicy") + } + } + return user.client.DeleteClouduser(user.UserName) +} + +type SCloudusers struct { + Users []SClouduser `xml:"Users>member"` + IsTruncated bool `xml:"IsTruncated"` + Marker string `xml:"Marker"` +} + +func (self *SAwsClient) GetCloudusers(marker string, maxItems int, pathPrefix string) (*SCloudusers, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + params := map[string]string{ + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + if len(pathPrefix) > 0 { + params["PathPrefix"] = pathPrefix + } + users := &SCloudusers{} + err := self.iamRequest("ListUsers", params, users) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.ListUsers") + } + return users, nil +} + +func (self *SAwsClient) CreateClouduser(path string, username string) (*SClouduser, error) { + params := map[string]string{ + "UserName": username, + } + if len(path) > 0 { + params["Path"] = path + } + user := struct { + User SClouduser `xml:"User"` + }{} + err := self.iamRequest("CreateUser", params, &user) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.CreateUser") + } + user.User.client = self + return &user.User, nil +} + +func (self *SAwsClient) DeleteClouduser(name string) error { + params := map[string]string{ + "UserName": name, + } + return self.iamRequest("DeleteUser", params, nil) +} + +type SClouduserPolicy struct { + Member []string `xml:"member"` +} + +type SClouduserPolicies struct { + PolicyNames SClouduserPolicy `xml:"PolicyNames"` + IsTruncated bool `xml:"IsTruncated"` +} + +func (self *SAwsClient) GetClouduserPolicies(userName string, marker string, maxItems int) (*SClouduserPolicies, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + params := map[string]string{ + "UserName": userName, + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + policies := SClouduserPolicies{} + err := self.iamRequest("ListUserPolicies", params, &policies) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.ListUserPolicies") + } + return &policies, nil +} + +type SPolicy struct { + PolicyName string `xml:"PolicyName"` + DefaultVersionId string `xml:"DefaultVersionId"` + PolicyId string `xml:"PolicyId"` + Path string `xml:"Path"` + Arn string `xml:"Arn"` + AttachmentCount int `xml:"AttachmentCount"` + CreateDate time.Time `xml:"CreateDate"` + UpdateDate time.Time `xml:"UpdateDate"` +} + +func (policy *SPolicy) GetName() string { + return policy.PolicyName +} + +func (policy *SPolicy) GetGlobalId() string { + return policy.Arn +} + +func (policy *SPolicy) GetPolicyType() string { + return policy.PolicyName +} + +func (policy *SPolicy) GetDescription() string { + return "" +} + +type SPolicies struct { + IsTruncated bool `xml:"IsTruncated"` + Marker string `xml:"Marker"` + Policies []SPolicy `xml:"Policies>member"` +} + +func (self *SAwsClient) GetPolicies(marker string, maxItems int, onlyAttached bool, pathPrefix string, policyUsageFilter string, scope string) (*SPolicies, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + + params := map[string]string{ + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + if onlyAttached { + params["OnlyAttached"] = "true" + } + if len(pathPrefix) > 0 { + params["PathPrefix"] = pathPrefix + } + if len(policyUsageFilter) > 0 { + params["PolicyUsageFilter"] = policyUsageFilter + } + if len(scope) > 0 { + params["Scope"] = scope + } + policies := &SPolicies{} + err := self.iamRequest("ListPolicies", params, policies) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.ListPolicies") + } + return policies, nil +} + +func (self *SAwsClient) AttachPolicy(userName string, policyArn string) error { + params := map[string]string{ + "PolicyArn": policyArn, + "UserName": userName, + } + return self.iamRequest("AttachUserPolicy", params, nil) +} + +func (self *SAwsClient) DetachPolicy(userName string, policyArn string) error { + params := map[string]string{ + "PolicyArn": policyArn, + "UserName": userName, + } + err := self.iamRequest("DetachUserPolicy", params, nil) + if err != nil && errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrap(err, "DetachUserPolicy") + } + return nil +} + +func (self *SAwsClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + user, err := self.CreateClouduser("", conf.Name) + if err != nil { + return nil, errors.Wrap(err, "CreateClouduser") + } + if len(conf.Password) > 0 { + _, err := self.CreateLoginProfile(conf.Name, conf.Password) + if err != nil { + log.Errorf("failed to create loginProfile for user %s error: %v", conf.Name, err) + } + } + return user, nil +} + +func (self *SAwsClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.GetClouduser(name) +} + +func (self *SAwsClient) GetClouduser(name string) (*SClouduser, error) { + user := struct { + User SClouduser `xml:"User"` + }{} + params := map[string]string{ + "UserName": name, + } + err := self.iamRequest("GetUser", params, &user) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.GetUser") + } + user.User.client = self + return &user.User, nil +} + +func (self *SAwsClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users := []SClouduser{} + marker := "" + for { + part, err := self.GetCloudusers(marker, 1000, "") + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + users = append(users, part.Users...) + if !part.IsTruncated { + break + } + marker = part.Marker + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = self + ret = append(ret, &users[i]) + } + return ret, nil +} + +type SAttachdPolicy struct { + client *SAwsClient + PolicyName string `xml:"PolicyName"` + PolicyArn string `xml:"PolicyArn"` +} + +func (policy *SAttachdPolicy) GetGlobalId() string { + return policy.PolicyArn +} + +func (policy *SAttachdPolicy) GetName() string { + return policy.PolicyName +} + +func (policy *SAttachdPolicy) GetPolicyType() string { + return policy.PolicyName +} + +func (policy *SAttachdPolicy) GetDescription() string { + return "" +} + +type SAttachdPolicies struct { + Marker string `xml:"Marker"` + AttachedPolicies []SAttachdPolicy `xml:"AttachedPolicies>member"` +} + +func (self *SAwsClient) GetAttachedPolicies(name string, marker string, maxItems int, pathPrefix string) (*SAttachdPolicies, error) { + if maxItems <= 0 || maxItems > 1000 { + maxItems = 1000 + } + params := map[string]string{ + "UserName": name, + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + if len(pathPrefix) > 0 { + params["PathPrefix"] = pathPrefix + } + policies := &SAttachdPolicies{} + err := self.iamRequest("ListAttachedUserPolicies", params, policies) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.ListAttachedUserPolicies") + } + return policies, nil +} + +type LoginProfile struct { + UserName string `xml:"UserName"` + CreateDate time.Time `xml:"CreateDate"` +} +type SLoginProfile struct { + LoginProfile LoginProfile `xml:"LoginProfile"` +} + +func (self *SAwsClient) GetLoginProfile(name string) (*SLoginProfile, error) { + params := map[string]string{ + "UserName": name, + } + loginProfix := &SLoginProfile{} + err := self.iamRequest("GetLoginProfile", params, loginProfix) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.GetLoginProfie") + } + return loginProfix, nil +} + +func (self *SAwsClient) DeleteLoginProfile(name string) error { + params := map[string]string{ + "UserName": name, + } + return self.iamRequest("DeleteLoginProfile", params, nil) +} + +func (self *SAwsClient) CreateLoginProfile(name, password string) (*SLoginProfile, error) { + params := map[string]string{ + "UserName": name, + "Password": password, + } + loginProfile := &SLoginProfile{} + err := self.iamRequest("CreateLoginProfile", params, loginProfile) + if err != nil { + return nil, errors.Wrap(err, "iamRequest.GetLoginProfie") + } + return loginProfile, nil +} + +func (self *SAwsClient) UpdateLoginProfile(name, password string) error { + params := map[string]string{ + "UserName": name, + "Password": password, + } + return self.iamRequest("UpdateLoginProfile", params, nil) +} + +func (self *SAwsClient) ResetClouduserPassword(name, password string) error { + _, err := self.GetLoginProfile(name) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + _, err = self.CreateLoginProfile(name, password) + return err + } + return errors.Wrap(err, "GetLoginProfile") + } + return self.UpdateLoginProfile(name, password) +} + +func (self *SAwsClient) ListGroupsForUser(name string, marker string, maxItems int) (*SCloudgroups, error) { + if maxItems < 1 || maxItems > 1000 { + maxItems = 1000 + } + params := map[string]string{ + "UserName": name, + "MaxItems": fmt.Sprintf("%d", maxItems), + } + if len(marker) > 0 { + params["Marker"] = marker + } + groups := &SCloudgroups{} + err := self.iamRequest("ListGroupsForUser", params, groups) + if err != nil { + return nil, errors.Wrap(err, "ListGroupsForUser") + } + return groups, nil +} diff --git a/pkg/multicloud/aws/provider/provider.go b/pkg/multicloud/aws/provider/provider.go index 69f4e05c97..8fbab353a3 100644 --- a/pkg/multicloud/aws/provider/provider.go +++ b/pkg/multicloud/aws/provider/provider.go @@ -43,6 +43,14 @@ func (self *SAwsProviderFactory) IsSupportPrepaidResources() bool { return false } +func (self *SAwsProviderFactory) IsSupportClouduser() bool { + return true +} + +func (self *SAwsProviderFactory) IsSupportCreateCloudgroup() bool { + return true +} + func (self *SAwsProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { output := cloudprovider.SCloudaccount{} if len(input.AccessKeyId) == 0 { @@ -117,6 +125,14 @@ func (self *SAwsProvider) GetAccountId() string { return self.client.GetAccountId() } +func (self *SAwsProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SAwsProvider) IsSupportCloudId() bool { + return cloudprovider.IsSupportCloudId(self) +} + func (self *SAwsProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } @@ -163,3 +179,31 @@ func (self *SAwsProvider) GetCloudRegionExternalIdPrefix() string { func (self *SAwsProvider) GetCapabilities() []string { return self.client.GetCapabilities() } + +func (self *SAwsProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SAwsProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SAwsProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SAwsProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} + +func (self *SAwsProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SAwsProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetICloudpolicies() +} + +func (self *SAwsProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} diff --git a/pkg/multicloud/aws/region.go b/pkg/multicloud/aws/region.go index c949039fcf..a4cb2b9643 100644 --- a/pkg/multicloud/aws/region.go +++ b/pkg/multicloud/aws/region.go @@ -80,6 +80,12 @@ const ( EC2_SERVICE_NAME = "ec2" EC2_SERVICE_ID = "EC2" + IAM_SERVICE_NAME = "iam" + IAM_SERVICE_ID = "IAM" + + STS_SERVICE_NAME = "sts" + STS_SERVICE_ID = "STS" + CLOUDWATCH_SERVICE_NAME = "monitoring" CLOUDWATCH_SERVICE_ID = "CloudWatch" ) @@ -256,32 +262,7 @@ func Build(r *request.Request) { } func (self *SRegion) rdsRequest(apiName string, params map[string]string, retval interface{}) error { - session, err := self.getAwsSession() - if err != nil { - return err - } - c := session.ClientConfig(RDS_SERVICE_NAME) - metadata := metadata.ClientInfo{ - ServiceName: RDS_SERVICE_NAME, - ServiceID: RDS_SERVICE_ID, - SigningName: c.SigningName, - SigningRegion: c.SigningRegion, - Endpoint: c.Endpoint, - APIVersion: "2014-10-31", - } - - if self.client.debug { - logLevel := aws.LogLevelType(uint(aws.LogDebugWithRequestErrors) + uint(aws.LogDebugWithHTTPBody)) - c.Config.LogLevel = &logLevel - } - - client := client.New(*c.Config, metadata, c.Handlers) - client.Handlers.Sign.PushBackNamed(v4.SignRequestHandler) - client.Handlers.Build.PushBackNamed(buildHandler) - client.Handlers.Unmarshal.PushBackNamed(UnmarshalHandler) - client.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler) - client.Handlers.UnmarshalError.PushBackNamed(query.UnmarshalErrorHandler) - return jsonRequest(client, apiName, params, retval, true) + return self.client.request(RDS_SERVICE_NAME, RDS_SERVICE_ID, "2014-10-31", apiName, params, retval) } func (self *SRegion) ec2Request(apiName string, params map[string]string, retval interface{}) error { diff --git a/pkg/multicloud/aws/shell/cloudgroup.go b/pkg/multicloud/aws/shell/cloudgroup.go new file mode 100644 index 0000000000..dba695e205 --- /dev/null +++ b/pkg/multicloud/aws/shell/cloudgroup.go @@ -0,0 +1,116 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aws" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + + type CloudgroupListOptions struct { + Marker string + MaxItems int + PathPrefix string + } + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List cloudgroups", func(cli *aws.SRegion, args *CloudgroupListOptions) error { + groups, err := cli.GetClient().ListGroups(args.Marker, args.MaxItems, args.PathPrefix) + if err != nil { + return err + } + printList(groups.Groups, 0, 0, 0, nil) + return nil + }) + + type CloudgroupCreateOptions struct { + NAME string + Path string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create cloudgroup", func(cli *aws.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Path) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupShowOptions struct { + NAME string + Marker string + MaxItems int + } + + shellutils.R(&CloudgroupShowOptions{}, "cloud-group-show", "Show cloudgroup details", func(cli *aws.SRegion, args *CloudgroupShowOptions) error { + group, err := cli.GetClient().GetGroup(args.NAME, args.Marker, args.MaxItems) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupOptions struct { + NAME string + } + + shellutils.R(&CloudgroupOptions{}, "cloud-group-delete", "Delete cloudgroup", func(cli *aws.SRegion, args *CloudgroupOptions) error { + return cli.GetClient().DeleteGroup(args.NAME) + }) + + type CloudgroupPolicyListOptions struct { + NAME string + Marker string + MaxItems int + } + + shellutils.R(&CloudgroupPolicyListOptions{}, "cloud-group-policy-list", "List cloudgroup policies", func(cli *aws.SRegion, args *CloudgroupPolicyListOptions) error { + policies, err := cli.GetClient().ListGroupPolicies(args.NAME, args.Marker, args.MaxItems) + if err != nil { + return err + } + printList(policies.Policies, 0, 0, 0, nil) + return nil + }) + + type GroupUserOptions struct { + GROUP string + USER string + } + + shellutils.R(&GroupUserOptions{}, "cloud-group-add-user", "Add user to cloudgroup", func(cli *aws.SRegion, args *GroupUserOptions) error { + return cli.GetClient().AddUserToGroup(args.GROUP, args.USER) + }) + + shellutils.R(&GroupUserOptions{}, "cloud-group-remove-user", "Remove user from cloudgroup", func(cli *aws.SRegion, args *GroupUserOptions) error { + return cli.GetClient().RemoveUserFromGroup(args.GROUP, args.USER) + }) + + type GroupPolicyOptions struct { + GROUP string + POLICY string + } + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-attach-policy", "Attach policy to cloudgroup", func(cli *aws.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().AttachGroupPolicy(args.GROUP, args.POLICY) + }) + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-detach-policy", "Detach policy from cloudgroup", func(cli *aws.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().DetachGroupPolicy(args.GROUP, args.POLICY) + }) + +} diff --git a/pkg/multicloud/aws/shell/clouduser.go b/pkg/multicloud/aws/shell/clouduser.go new file mode 100644 index 0000000000..109c2bf7c1 --- /dev/null +++ b/pkg/multicloud/aws/shell/clouduser.go @@ -0,0 +1,161 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aws" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserListOptions struct { + Marker string + MaxItems int + PathPrefix string + } + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List cloudusers", func(cli *aws.SRegion, args *ClouduserListOptions) error { + users, err := cli.GetClient().GetCloudusers(args.Marker, args.MaxItems, args.PathPrefix) + if err != nil { + return err + } + printList(users.Users, 0, 0, 0, nil) + return nil + }) + + type ClouduserCreateOptions struct { + NAME string + Path string + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create clouduser", func(cli *aws.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateClouduser(args.Path, args.NAME) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type ClouduserOptions struct { + NAME string + } + + shellutils.R(&ClouduserOptions{}, "cloud-user-show", "Show clouduser details", func(cli *aws.SRegion, args *ClouduserOptions) error { + user, err := cli.GetClient().GetClouduser(args.NAME) + if err != nil { + return err + } + printObject(user) + return nil + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-loginprofile", "Show clouduser loginprofile", func(cli *aws.SRegion, args *ClouduserOptions) error { + profile, err := cli.GetClient().GetLoginProfile(args.NAME) + if err != nil { + return err + } + printObject(profile) + return nil + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-delete-loginprofile", "Delete clouduser loginprofile", func(cli *aws.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteLoginProfile(args.NAME) + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-delete", "Delete clouduser", func(cli *aws.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteClouduser(args.NAME) + }) + + type ClouduserLoginProfileCreateOptions struct { + NAME string + PASSWORD string + } + + shellutils.R(&ClouduserLoginProfileCreateOptions{}, "cloud-user-create-loginprofile", "Create clouduser loginprofile", func(cli *aws.SRegion, args *ClouduserLoginProfileCreateOptions) error { + profile, err := cli.GetClient().CreateLoginProfile(args.NAME, args.PASSWORD) + if err != nil { + return err + } + printObject(profile) + return nil + }) + + shellutils.R(&ClouduserLoginProfileCreateOptions{}, "cloud-user-reset-password", "Reset clouduser password", func(cli *aws.SRegion, args *ClouduserLoginProfileCreateOptions) error { + return cli.GetClient().ResetClouduserPassword(args.NAME, args.PASSWORD) + }) + + type ClouduserPolicyListOptions struct { + NAME string + Marker string + MaxItems int + } + + shellutils.R(&ClouduserPolicyListOptions{}, "cloud-user-policy-list", "List clouduser policies", func(cli *aws.SRegion, args *ClouduserPolicyListOptions) error { + policies, err := cli.GetClient().GetClouduserPolicies(args.NAME, args.Marker, args.MaxItems) + if err != nil { + return err + } + printObject(policies) + return nil + }) + + type ClouduserAttachedPolicyListOptions struct { + NAME string + Marker string + MaxItems int + PathPrefix string + } + + shellutils.R(&ClouduserAttachedPolicyListOptions{}, "cloud-user-attachd-policy-list", "List clouduser attached policies", func(cli *aws.SRegion, args *ClouduserAttachedPolicyListOptions) error { + policies, err := cli.GetClient().GetAttachedPolicies(args.NAME, args.Marker, args.MaxItems, args.PathPrefix) + if err != nil { + return err + } + printList(policies.AttachedPolicies, 0, 0, 0, nil) + return nil + }) + + type PolicyListOptions struct { + MaxItems int + Marker string + OnlyAttached bool + PathPrefix string + PolicyUsageFilter string `choices:"PermissionsPolicy|PermissionsBoundary"` + Scope string `choices:"All|AWS|Local"` + } + + shellutils.R(&PolicyListOptions{}, "cloud-policy-list", "List policies", func(cli *aws.SRegion, args *PolicyListOptions) error { + policies, err := cli.GetClient().GetPolicies(args.Marker, args.MaxItems, args.OnlyAttached, args.PathPrefix, args.PolicyUsageFilter, args.Scope) + if err != nil { + return err + } + printList(policies.Policies, 0, 0, 0, nil) + return nil + }) + + type ClouduserPolicyOptions struct { + NAME string + POLICY_ARN string + } + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-attach-policy", "Attach policy for clouduser", func(cli *aws.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().AttachPolicy(args.NAME, args.POLICY_ARN) + }) + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-detach-policy", "Detach policy from clouduser", func(cli *aws.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().DetachPolicy(args.NAME, args.POLICY_ARN) + }) + +} diff --git a/pkg/multicloud/aws/shell/sts.go b/pkg/multicloud/aws/shell/sts.go new file mode 100644 index 0000000000..c7fe0538e8 --- /dev/null +++ b/pkg/multicloud/aws/shell/sts.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aws" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CallerIdentityOptions struct { + } + shellutils.R(&CallerIdentityOptions{}, "caller-identity-show", "Show caller identity", func(cli *aws.SRegion, args *CallerIdentityOptions) error { + identity, err := cli.GetClient().GetCallerIdentity() + if err != nil { + return err + } + printObject(identity) + return nil + }) +} diff --git a/pkg/multicloud/aws/sts.go b/pkg/multicloud/aws/sts.go new file mode 100644 index 0000000000..f436ea7e55 --- /dev/null +++ b/pkg/multicloud/aws/sts.go @@ -0,0 +1,32 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package aws + +import "yunion.io/x/pkg/errors" + +type SCallerIdentity struct { + Arn string `xml:"Arn"` + UserId string `xml:"UserId"` + Account string `xml:"Account"` +} + +func (self *SAwsClient) GetCallerIdentity() (*SCallerIdentity, error) { + ret := &SCallerIdentity{} + err := self.stsRequest("GetCallerIdentity", nil, ret) + if err != nil { + return nil, errors.Wrap(err, "GetCallerIdentity") + } + return ret, nil +} diff --git a/pkg/multicloud/azure/azure.go b/pkg/multicloud/azure/azure.go index c0d2c3103a..a7a7380a83 100644 --- a/pkg/multicloud/azure/azure.go +++ b/pkg/multicloud/azure/azure.go @@ -18,6 +18,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" net_url "net/url" "strconv" "strings" @@ -42,6 +43,13 @@ const ( AZURE_API_VERSION = "2016-02-01" ) +type TAzureResource string + +var ( + GraphResource = TAzureResource("graph") + DefaultResource = TAzureResource("default") +) + type SAzureClient struct { *AzureClientConfig @@ -88,6 +96,13 @@ var DEFAULT_API_VERSION = map[string]string{ "Microsoft.Authorization/policyAssignments": "2019-09-01", } +var GRAPH_API_VERSION = map[string]string{ + "Microsoft.DirectoryServices.User": "1.6", + "users": "1.6", + "Microsoft.DirectoryServices.Group": "1.6", + "groups": "1.6", +} + type AzureClientConfig struct { cpcfg cloudprovider.ProviderConfig @@ -144,7 +159,7 @@ func NewAzureClient(cfg *AzureClientConfig) (*SAzureClient, error) { return &client, nil } -func (self *SAzureClient) getDefaultClient() (*autorest.Client, error) { +func (self *SAzureClient) getClient(resource TAzureResource) (*autorest.Client, error) { client := autorest.NewClientWithUserAgent("Yunion API") conf := auth.NewClientCredentialsConfig(self.clientId, self.clientSecret, self.tenantId) env, err := azureenv.EnvironmentFromName(self.envName) @@ -156,8 +171,14 @@ func (self *SAzureClient) getDefaultClient() (*autorest.Client, error) { client.Sender = httpClient self.env = env - self.domain = env.ResourceManagerEndpoint - conf.Resource = env.ResourceManagerEndpoint + switch resource { + case GraphResource: + self.domain = env.GraphEndpoint + conf.Resource = env.GraphEndpoint + default: + self.domain = env.ResourceManagerEndpoint + conf.Resource = env.ResourceManagerEndpoint + } conf.AADEndpoint = env.ActiveDirectoryEndpoint { spt, err := conf.ServicePrincipalToken() @@ -175,12 +196,20 @@ func (self *SAzureClient) getDefaultClient() (*autorest.Client, error) { return &client, nil } +func (self *SAzureClient) getDefaultClient() (*autorest.Client, error) { + return self.getClient(DefaultResource) +} + +func (self *SAzureClient) getGraphClient() (*autorest.Client, error) { + return self.getClient(GraphResource) +} + func (self *SAzureClient) jsonRequest(method, url string, body string) (jsonutils.JSONObject, error) { cli, err := self.getDefaultClient() if err != nil { return nil, err } - return jsonRequest(cli, method, self.domain, url, self.subscriptionId, body) + return jsonRequest(cli, method, self.domain, url, self.subscriptionId, body, DefaultResource) } func (self *SAzureClient) Put(url string, body jsonutils.JSONObject) error { @@ -188,7 +217,7 @@ func (self *SAzureClient) Put(url string, body jsonutils.JSONObject) error { if err != nil { return err } - resp, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String()) + resp, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String(), DefaultResource) if err != nil { return err } @@ -203,7 +232,7 @@ func (self *SAzureClient) Patch(url string, body jsonutils.JSONObject) error { if err != nil { return err } - resp, err := jsonRequest(cli, "PATCH", self.domain, url, self.subscriptionId, body.String()) + resp, err := jsonRequest(cli, "PATCH", self.domain, url, self.subscriptionId, body.String(), DefaultResource) if err != nil { return err } @@ -225,7 +254,7 @@ func (self *SAzureClient) Get(resourceId string, params []string, retVal interfa if err != nil { return err } - body, err := jsonRequest(cli, "GET", self.domain, path, self.subscriptionId, "") + body, err := jsonRequest(cli, "GET", self.domain, path, self.subscriptionId, "", DefaultResource) if err != nil { return err } @@ -246,7 +275,7 @@ func (self *SAzureClient) ListVmSizes(location string) (jsonutils.JSONObject, er return nil, fmt.Errorf("need subscription id") } url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/locations/%s/vmSizes", self.subscriptionId, location) - return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) } func (self *SAzureClient) ListClassicDisks() (jsonutils.JSONObject, error) { @@ -258,7 +287,7 @@ func (self *SAzureClient) ListClassicDisks() (jsonutils.JSONObject, error) { return nil, fmt.Errorf("need subscription id") } url := fmt.Sprintf("/subscriptions/%s/services/disks", self.subscriptionId) - return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + return jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) } func (self *SAzureClient) ListAll(resourceType string, retVal interface{}) error { @@ -269,6 +298,45 @@ func (self *SAzureClient) ListAllWithNextToken(resourceType string, retVal inter return self.ListResourcesWithNextLink(resourceType, retVal, []string{"value"}) } +func (self *SAzureClient) ListGraphResource(resource string, params url.Values, retVal interface{}) error { + cli, err := self.getGraphClient() + if err != nil { + return err + } + if params == nil { + params = url.Values{} + } + params.Set("api-version", "1.6") + url := fmt.Sprintf("%s/%s?%s", self.tenantId, resource, params.Encode()) + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", GraphResource) + if err != nil { + return err + } + if retVal != nil { + err = body.Unmarshal(retVal, "value") + if err != nil { + return err + } + } + return nil +} + +func (self *SAzureClient) CreateGraphResource(resource string, body jsonutils.JSONObject, retVal interface{}) error { + cli, err := self.getGraphClient() + if err != nil { + return err + } + url := fmt.Sprintf("%s/%s?api-version=1.6", self.tenantId, resource) + result, err := jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body.String(), GraphResource) + if err != nil { + return err + } + if retVal != nil { + return result.Unmarshal(retVal) + } + return nil +} + func (self *SAzureClient) ListResourcesWithNextLink(resourceType string, retVal interface{}, keys []string) (string, error) { cli, err := self.getDefaultClient() if err != nil { @@ -281,7 +349,7 @@ func (self *SAzureClient) ListResourcesWithNextLink(resourceType string, retVal if len(resourceType) > 0 { url += fmt.Sprintf("/providers/%s", resourceType) } - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) if err != nil { return "", err } @@ -320,7 +388,7 @@ func (self *SAzureClient) ListResourcesOfMetirc(resourceType string, external_id url += fmt.Sprintf("?%s", values.Encode()) } //body, err := jsonRequest(cli, "GET", "https://management.azure.com", url, self.subscriptionId, "") - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) if err != nil { return nil, err } @@ -332,7 +400,7 @@ func (self *SAzureClient) ListSubscriptions() (jsonutils.JSONObject, error) { if err != nil { return nil, err } - return jsonRequest(cli, "GET", self.domain, "/subscriptions", self.subscriptionId, "") + return jsonRequest(cli, "GET", self.domain, "/subscriptions", self.subscriptionId, "", DefaultResource) } func (self *SAzureClient) List(golbalResource string, retVal interface{}) error { @@ -347,7 +415,7 @@ func (self *SAzureClient) List(golbalResource string, retVal interface{}) error if len(self.subscriptionId) > 0 && len(golbalResource) > 0 { url += fmt.Sprintf("/%s", golbalResource) } - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) if err != nil { return err } @@ -363,7 +431,7 @@ func (self *SAzureClient) ListByTypeWithResourceGroup(resourceGroupName string, return fmt.Errorf("Missing subscription Info") } url := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s", self.subscriptionId, resourceGroupName, Type) - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) if err != nil { return err } @@ -375,7 +443,16 @@ func (self *SAzureClient) Delete(resourceId string) error { if err != nil { return err } - _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, self.subscriptionId, "") + _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, self.subscriptionId, "", DefaultResource) + return err +} + +func (self *SAzureClient) DeleteGraph(resourceId string) error { + cli, err := self.getGraphClient() + if err != nil { + return err + } + _, err = jsonRequest(cli, "DELETE", self.domain, resourceId, self.subscriptionId, "", GraphResource) return err } @@ -385,7 +462,7 @@ func (self *SAzureClient) PerformAction(resourceId string, action string, body s return nil, err } url := fmt.Sprintf("%s/%s", resourceId, action) - return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body) + return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body, DefaultResource) } func (self *SAzureClient) CreateResourceGroup(name, location string) (*SResourceGroup, error) { @@ -401,7 +478,7 @@ func (self *SAzureClient) CreateResourceGroup(name, location string) (*SResource } //Create resourceGroup _url := fmt.Sprintf("/subscriptions/%s/resourcegroups/%s", self.subscriptionId, name) - _, err = jsonRequest(cli, "PUT", self.domain, _url, self.subscriptionId, fmt.Sprintf(`{"name": "%s", "location": "%s"}`, name, location)) + _, err = jsonRequest(cli, "PUT", self.domain, _url, self.subscriptionId, fmt.Sprintf(`{"name": "%s", "location": "%s"}`, name, location), DefaultResource) if err != nil { return nil, errors.Wrap(err, "jsonRequest") } @@ -456,7 +533,7 @@ func (e *AzureError) Error() string { func (self *SAzureClient) getUniqName(cli *autorest.Client, resourceGroup string, resourceType, name string, body jsonutils.JSONObject) (string, string, error) { url := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s", self.subscriptionId, resourceGroup, resourceType, name) - if _, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, ""); err != nil { + if _, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource); err != nil { if err == cloudprovider.ErrNotFound { return url, body.String(), nil } @@ -464,7 +541,7 @@ func (self *SAzureClient) getUniqName(cli *autorest.Client, resourceGroup string } for i := 0; i < 20; i++ { url = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/%s/%s-%d", self.subscriptionId, resourceGroup, resourceType, name, i) - if _, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, ""); err == cloudprovider.ErrNotFound { + if _, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource); err == cloudprovider.ErrNotFound { if err == cloudprovider.ErrNotFound { data := body.(*jsonutils.JSONDict) data.Set("name", jsonutils.NewString(fmt.Sprintf("%s-%d", name, i))) @@ -528,7 +605,7 @@ func (self *SAzureClient) CreateWithResourceGroup(resourceGroup string, body jso return err } - result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, reqString) + result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, reqString, DefaultResource) if err != nil { return err } @@ -552,7 +629,7 @@ func (self *SAzureClient) CheckNameAvailability(Type string, body string) (jsonu return nil, fmt.Errorf("Missing subscription ID") } url := fmt.Sprintf("/subscriptions/%s/providers/%s/checkNameAvailability", self.subscriptionId, Type) - return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body) + return jsonRequest(cli, "POST", self.domain, url, self.subscriptionId, body, DefaultResource) } func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) error { @@ -564,7 +641,7 @@ func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) if err != nil { return errors.Wrap(err, "failed to found id for update operation") } - result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String()) + result, err := jsonRequest(cli, "PUT", self.domain, url, self.subscriptionId, body.String(), DefaultResource) if err != nil { return err } @@ -576,7 +653,7 @@ func (self *SAzureClient) Update(body jsonutils.JSONObject, retVal interface{}) func waitRegisterComplete(client *autorest.Client, domain, subscriptionId string, serviceType string) error { for i := 1; i < 10; i++ { - result, err := _jsonRequest(client, "GET", domain, fmt.Sprintf("/subscriptions/%s/providers", subscriptionId), "") + result, err := _jsonRequest(client, "GET", domain, fmt.Sprintf("/subscriptions/%s/providers", subscriptionId), "", DefaultResource) if err != nil { return err } @@ -601,7 +678,7 @@ func waitRegisterComplete(client *autorest.Client, domain, subscriptionId string func registerService(client *autorest.Client, domain, subscriptionId string, serviceType string) error { registryUrl := fmt.Sprintf("/subscriptions/%s/providers/%s/register", subscriptionId, serviceType) - result, err := _jsonRequest(client, "POST", domain, registryUrl, "") + result, err := _jsonRequest(client, "POST", domain, registryUrl, "", DefaultResource) if err != nil || result.Contains("error") { return fmt.Errorf("failed to register %s service", serviceType) } @@ -636,21 +713,24 @@ func recoverFromError(client *autorest.Client, domain, subscriptionId string, az } } -func jsonRequest(client *autorest.Client, method, domain, baseUrl string, subscriptionId string, body string) (jsonutils.JSONObject, error) { - result, err := _jsonRequest(client, method, domain, baseUrl, body) +func jsonRequest(client *autorest.Client, method, domain, baseUrl string, subscriptionId string, body string, resourceType TAzureResource) (jsonutils.JSONObject, error) { + result, err := _jsonRequest(client, method, domain, baseUrl, body, resourceType) if err != nil { return nil, err } - if result.Contains("error") { - azureError := AzureError{} - if err := result.Unmarshal(&azureError, "error"); err != nil { + for _, errKey := range []string{"error", "odata.error"} { + if result.Contains(errKey) { + azureError := AzureError{} + err := result.Unmarshal(&azureError, errKey) + if err != nil { + return nil, fmt.Errorf(result.String()) + } + if recoverFromError(client, domain, subscriptionId, azureError) { + return _jsonRequest(client, method, domain, baseUrl, body, resourceType) + } + log.Errorf("Azure %s request: %s \nbody: %s error: %v", method, baseUrl, body, result.String()) return nil, fmt.Errorf(result.String()) } - if recoverFromError(client, domain, subscriptionId, azureError) { - return _jsonRequest(client, method, domain, baseUrl, body) - } - log.Errorf("Azure %s request: %s \nbody: %s error: %v", method, baseUrl, body, result.String()) - return nil, fmt.Errorf(result.String()) } return result, nil } @@ -664,6 +744,9 @@ func waitForComplatetion(client *autorest.Client, req *http.Request, resp *http. location = asyncoperation } for { + if strings.HasSuffix(location, "Microsoft.DirectoryServices.User") || strings.HasSuffix(location, "Microsoft.DirectoryServices.Group") { + location = location + "?api-version=1.6" + } asyncReq, err := http.NewRequest("GET", location, nil) if err != nil { return nil, err @@ -745,11 +828,16 @@ func waitForComplatetion(client *autorest.Client, req *http.Request, resp *http. return nil, nil } -func _jsonRequest(client *autorest.Client, method, domain, baseURL, body string) (result jsonutils.JSONObject, err error) { +func _jsonRequest(client *autorest.Client, method, domain, baseURL, body string, resourceType TAzureResource) (result jsonutils.JSONObject, err error) { version := AZURE_API_VERSION - for resourceType, _version := range DEFAULT_API_VERSION { - if strings.Index(strings.ToLower(baseURL), strings.ToLower(resourceType)) > 0 { - version = _version + switch resourceType { + case GraphResource: + version = "1.6" + default: + for resourceType, _version := range DEFAULT_API_VERSION { + if strings.Index(strings.ToLower(baseURL), strings.ToLower(resourceType)) > 0 { + version = _version + } } } url := fmt.Sprintf("%s%s?api-version=%s", domain, baseURL, version) @@ -925,6 +1013,15 @@ func (self *SAzureClient) GetAccountId() string { return self.tenantId } +func (self *SAzureClient) GetIamLoginUrl() string { + switch self.envName { + case "AzureChinaCloud": + return "http://portal.azure.cn" + default: + return "http://portal.azure.com" + } +} + func (self *SAzureClient) GetIRegions() []cloudprovider.ICloudRegion { return self.iregions } diff --git a/pkg/multicloud/azure/cloudgroup.go b/pkg/multicloud/azure/cloudgroup.go new file mode 100644 index 0000000000..02dc1d85c4 --- /dev/null +++ b/pkg/multicloud/azure/cloudgroup.go @@ -0,0 +1,205 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package azure + +import ( + "fmt" + "net/url" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SCloudgroup struct { + client *SAzureClient + + OdataType string `json:"odata.type"` + ObjectType string + ObjectId string + DeletionTimestamp string + Description string + DirSyncEnabled string + DisplayName string + LastDirSyncTime string + Mail string + MailNickname string + MailEnabled bool + ProxyAddresses []string +} + +func (group *SCloudgroup) GetName() string { + return group.DisplayName +} + +func (group *SCloudgroup) GetGlobalId() string { + return group.ObjectId +} + +func (group *SCloudgroup) GetDescription() string { + return group.Description +} + +func (group *SCloudgroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (group *SCloudgroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := group.client.ListGroupMemebers(group.ObjectId) + if err != nil { + return nil, errors.Wrap(err, "ListGroupMemebers") + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = group.client + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (group *SCloudgroup) AddUser(name string) error { + return group.client.AddGroupUser(group.ObjectId, name) +} + +func (group *SCloudgroup) RemoveUser(name string) error { + return group.client.RemoveGroupUser(group.ObjectId, name) +} + +func (group *SCloudgroup) AttachSystemPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (group *SCloudgroup) DetachSystemPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (group *SCloudgroup) Delete() error { + return group.client.DeleteGroup(group.ObjectId) +} + +func (self *SAzureClient) GetCloudgroups(name string) ([]SCloudgroup, error) { + groups := []SCloudgroup{} + params := url.Values{} + if len(name) > 0 { + params.Set("$filter", fmt.Sprintf("displayName eq '%s'", name)) + } + err := self.ListGraphResource("groups", params, &groups) + if err != nil { + return nil, err + } + return groups, nil +} + +func (self *SAzureClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := self.GetCloudgroups("") + if err != nil { + return nil, errors.Wrap(err, "GetCloudgroups") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = self + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (self *SAzureClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + groups, err := self.GetCloudgroups(name) + if err != nil { + return nil, errors.Wrapf(err, "GetCloudgroups(%s)", name) + } + if len(groups) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(groups) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + groups[0].client = self + return &groups[0], nil +} + +func (self *SAzureClient) ListGroupMemebers(id string) ([]SClouduser, error) { + users := []SClouduser{} + resource := fmt.Sprintf("groups/%s/members", id) + err := self.ListGraphResource(resource, nil, &users) + if err != nil { + return nil, err + } + return users, nil +} + +func (self *SAzureClient) DeleteGroup(id string) error { + return self.DeleteGraph(fmt.Sprintf("%s/groups/%s?api-version=1.6", self.tenantId, id)) +} + +func (self *SAzureClient) CreateGroup(name, desc string) (*SCloudgroup, error) { + params := map[string]interface{}{ + "displayName": name, + "mailNickname": name, + "mailEnabled": false, + "securityEnabled": true, + } + if len(desc) > 0 { + params["Description"] = desc + } + group := SCloudgroup{client: self} + err := self.CreateGraphResource("groups", jsonutils.Marshal(params), &group) + if err != nil { + return nil, errors.Wrap(err, "Create") + } + return &group, nil +} + +func (self *SAzureClient) RemoveGroupUser(id, userName string) error { + users, err := self.GetCloudusers(userName) + if err != nil { + return errors.Wrapf(err, "GetCloudusers(%s)", userName) + } + if len(users) == 0 { + return nil + } + if len(users) > 1 { + return cloudprovider.ErrDuplicateId + } + return self.DeleteGraph(fmt.Sprintf("%s/groups/%s/$links/members/%s", self.tenantId, id, users[0].ObjectId)) +} + +func (self *SAzureClient) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.CreateGroup(name, desc) +} + +func (self *SAzureClient) AddGroupUser(id, userName string) error { + users, err := self.GetCloudusers(userName) + if err != nil { + return errors.Wrapf(err, "GetCloudusers(%s)", userName) + } + if len(users) == 0 { + return nil + } + if len(users) > 1 { + return cloudprovider.ErrDuplicateId + } + resource := fmt.Sprintf("groups/%s/$links/members", id) + params := map[string]string{ + "url": fmt.Sprintf("%s%s/directoryObjects/%s", self.domain, self.tenantId, users[0].ObjectId), + } + err = self.CreateGraphResource(resource, jsonutils.Marshal(params), nil) + if err != nil && !strings.Contains(err.Error(), "One or more added object references already exist for the following modified properties") { + return err + } + return nil +} diff --git a/pkg/multicloud/azure/clouduser.go b/pkg/multicloud/azure/clouduser.go new file mode 100644 index 0000000000..4a999ee850 --- /dev/null +++ b/pkg/multicloud/azure/clouduser.go @@ -0,0 +1,256 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package azure + +import ( + "fmt" + "net/url" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SClouduserPasswordProfile struct { + Password string + forceChangePasswordNextLogin bool + enforceChangePasswordPolicy bool +} + +type SClouduser struct { + client *SAzureClient + + OdataType string `json:"odata.type"` + ObjectType string + ObjectId string + DeletionTimestamp string + AccountEnabled bool + AgeGroup string + City string + CompanyName string + ConsentProvidedForMinor string + Country string + CreatedDateTime time.Time + CreationType string + Department string + DirSyncEnabled string + DisplayName string + EmployeeId string + FacsimileTelephoneNumber string + GivenName string + ImmutableId string + IsCompromised string + JobTitle string + LastDirSyncTime string + LegalAgeGroupClassification string + Mail string + MailNickname string + Mobile string + OnPremisesDistinguishedName string + OnPremisesSecurityIdentifier string + PasswordPolicies string + PasswordProfile SClouduserPasswordProfile + PhysicalDeliveryOfficeName string + PostalCode string + PreferredLanguage string + RefreshTokensValidFromDateTime time.Time + ShowInAddressList string + SipProxyAddress string + State string + StreetAddress string + Surname string + TelephoneNumber string + ThumbnailPhotoOdataMediaEditLink string `json:"thumbnailPhoto@odata.mediaEditLink"` + UsageLocation string + UserPrincipalName string + UserState string + UserStateChangedOn string + UserType string +} + +func (user *SClouduser) GetName() string { + return user.UserPrincipalName +} + +func (user *SClouduser) GetGlobalId() string { + return user.ObjectId +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (user *SClouduser) AttachSystemPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) DetachSystemPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) IsConsoleLogin() bool { + return user.AccountEnabled +} + +// 需要当前应用有User administrator权限 +func (user *SClouduser) Delete() error { + return user.client.DeleteClouduser(user.ObjectId) +} + +func (user *SClouduser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.ObjectId, password) +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := user.client.GetUserGroups(user.ObjectId) + if err != nil { + return nil, errors.Wrap(err, "GetUserGroups") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (self *SAzureClient) GetUserGroups(userId string) ([]SCloudgroup, error) { + cli, err := self.getGraphClient() + if err != nil { + return nil, err + } + + resource := fmt.Sprintf("%s/users/%s/memberOf", self.tenantId, userId) + resp, err := jsonRequest(cli, "GET", self.domain, resource, self.subscriptionId, "", GraphResource) + if err != nil { + return nil, err + } + + groups := []SCloudgroup{} + err = resp.Unmarshal(&groups, "value") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return groups, nil +} + +func (self *SAzureClient) ResetClouduserPassword(id, password string) error { + cli, err := self.getGraphClient() + if err != nil { + return err + } + body := jsonutils.Marshal(map[string]interface{}{ + "passwordPolicies": "DisablePasswordExpiration, DisableStrongPassword", + "passwordProfile": map[string]interface{}{ + "password": password, + }, + }) + resource := fmt.Sprintf("%s/users/%s", self.tenantId, id) + _, err = jsonRequest(cli, "PATCH", self.domain, resource, self.subscriptionId, body.String(), GraphResource) + return err +} + +func (self *SAzureClient) GetCloudusers(name string) ([]SClouduser, error) { + users := []SClouduser{} + params := url.Values{} + if len(name) > 0 { + params.Set("$filter", fmt.Sprintf("userPrincipalName eq '%s'", name)) + } + err := self.ListGraphResource("users", params, &users) + if err != nil { + return nil, err + } + return users, nil +} + +func (self *SAzureClient) DeleteClouduser(id string) error { + return self.DeleteGraph(fmt.Sprintf("%s/users/%s?api-version=1.6", self.tenantId, id)) +} + +func (self *SAzureClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := self.GetCloudusers("") + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = self + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (self *SAzureClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + users, err := self.GetCloudusers(name) + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + if len(users) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(users) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + users[0].client = self + return &users[0], nil +} + +func (self *SAzureClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.CreateClouduser(conf.Name, conf.Password) +} + +type SDomain struct { + Name string +} + +func (self *SAzureClient) GetDomains() ([]SDomain, error) { + domains := []SDomain{} + err := self.ListGraphResource("domains", nil, &domains) + if err != nil { + return nil, errors.Wrap(err, "ListGraphResource") + } + return domains, nil +} + +func (self *SAzureClient) CreateClouduser(name, password string) (*SClouduser, error) { + passwordProfile := map[string]interface{}{ + "password": "Lomo1824", + } + if len(password) > 0 { + passwordProfile["password"] = password + } + params := map[string]interface{}{ + "accountEnabled": true, + "displayName": name, + "mailNickname": name, + "passwordProfile": passwordProfile, + "userPrincipalName": name, + } + domains, err := self.GetDomains() + if err != nil { + return nil, errors.Wrap(err, "GetDomains") + } + if len(domains) == 0 { + return nil, errors.Wrap(err, "Missing domains") + } + params["userPrincipalName"] = fmt.Sprintf("%s@%s", name, domains[0].Name) + user := SClouduser{client: self} + err = self.CreateGraphResource("users", jsonutils.Marshal(params), &user) + if err != nil { + return nil, errors.Wrap(err, "Create") + } + return &user, nil +} diff --git a/pkg/multicloud/azure/provider/provider.go b/pkg/multicloud/azure/provider/provider.go index b68c0cc97f..07ebe3332d 100644 --- a/pkg/multicloud/azure/provider/provider.go +++ b/pkg/multicloud/azure/provider/provider.go @@ -62,6 +62,14 @@ func (self *SAzureProviderFactory) IsSupportPrepaidResources() bool { return false } +func (self *SAzureProviderFactory) IsSupportClouduser() bool { + return true +} + +func (self *SAzureProviderFactory) IsSupportCreateCloudgroup() bool { + return true +} + func (self *SAzureProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { output := cloudprovider.SCloudaccount{} if len(input.DirectoryId) == 0 { @@ -169,6 +177,14 @@ func (self *SAzureProvider) GetAccountId() string { return self.client.GetAccountId() } +func (self *SAzureProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SAzureProvider) IsSupportCloudId() bool { + return cloudprovider.IsSupportCloudId(self) +} + func (self *SAzureProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } @@ -205,3 +221,27 @@ func (self *SAzureProvider) GetCloudRegionExternalIdPrefix() string { func (self *SAzureProvider) GetCapabilities() []string { return self.client.GetCapabilities() } + +func (self *SAzureProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SAzureProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SAzureProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} + +func (self *SAzureProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SAzureProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SAzureProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} diff --git a/pkg/multicloud/azure/resourcesku.go b/pkg/multicloud/azure/resourcesku.go index 6c3162ac25..18438e4252 100644 --- a/pkg/multicloud/azure/resourcesku.go +++ b/pkg/multicloud/azure/resourcesku.go @@ -123,7 +123,7 @@ func (self *SAzureClient) ListResourceSkus() ([]SResourceSku, error) { url := fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Compute/skus?api-version=2017-09-01", self.subscriptionId) skus := make([]SResourceSku, 0) for { - body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "") + body, err := jsonRequest(cli, "GET", self.domain, url, self.subscriptionId, "", DefaultResource) if err != nil { return nil, err } diff --git a/pkg/multicloud/azure/shell/cloudgroup.go b/pkg/multicloud/azure/shell/cloudgroup.go new file mode 100644 index 0000000000..14cc2314e1 --- /dev/null +++ b/pkg/multicloud/azure/shell/cloudgroup.go @@ -0,0 +1,83 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CloudgroupListOptions struct { + Name string + } + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List cloudgroups", func(cli *azure.SRegion, args *CloudgroupListOptions) error { + groups, err := cli.GetClient().GetCloudgroups(args.Name) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type CloudgroupUserListOptions struct { + ID string + } + + shellutils.R(&CloudgroupUserListOptions{}, "cloud-group-user-list", "List group cloudusers", func(cli *azure.SRegion, args *CloudgroupUserListOptions) error { + users, err := cli.GetClient().ListGroupMemebers(args.ID) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type CloudgroupIdOptions struct { + ID string + } + + shellutils.R(&CloudgroupIdOptions{}, "cloud-group-delete", "Delete cloudgroup", func(cli *azure.SRegion, args *CloudgroupIdOptions) error { + return cli.GetClient().DeleteGroup(args.ID) + }) + + type CloudgroupCreateOptions struct { + NAME string + Desc string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create cloudgroup", func(cli *azure.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Desc) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupUserOptions struct { + ID string + USER_NAME string + } + + shellutils.R(&CloudgroupUserOptions{}, "cloud-group-remove-user", "Remove user from cloudgroup", func(cli *azure.SRegion, args *CloudgroupUserOptions) error { + return cli.GetClient().RemoveGroupUser(args.ID, args.USER_NAME) + }) + + shellutils.R(&CloudgroupUserOptions{}, "cloud-group-add-user", "Add user to cloudgroup", func(cli *azure.SRegion, args *CloudgroupUserOptions) error { + return cli.GetClient().AddGroupUser(args.ID, args.USER_NAME) + }) + +} diff --git a/pkg/multicloud/azure/shell/clouduser.go b/pkg/multicloud/azure/shell/clouduser.go new file mode 100644 index 0000000000..aad7761513 --- /dev/null +++ b/pkg/multicloud/azure/shell/clouduser.go @@ -0,0 +1,87 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserListOptions struct { + Name string + } + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List cloudusers", func(cli *azure.SRegion, args *ClouduserListOptions) error { + users, err := cli.GetClient().GetCloudusers(args.Name) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type ClouduserIdOptions struct { + ID string + } + + shellutils.R(&ClouduserIdOptions{}, "cloud-user-delete", "Delete clouduser", func(cli *azure.SRegion, args *ClouduserIdOptions) error { + return cli.GetClient().DeleteClouduser(args.ID) + }) + + shellutils.R(&ClouduserIdOptions{}, "cloud-user-group-list", "List clouduser groups", func(cli *azure.SRegion, args *ClouduserIdOptions) error { + groups, err := cli.GetClient().GetUserGroups(args.ID) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type ClouduserCreateOptions struct { + NAME string + Password string + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create clouduser", func(cli *azure.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateClouduser(args.NAME, args.Password) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type ClouduserResetPasswordOptions struct { + NAME string + PASSWORD string + } + + shellutils.R(&ClouduserResetPasswordOptions{}, "cloud-user-reset-password", "Reset clouduser password", func(cli *azure.SRegion, args *ClouduserResetPasswordOptions) error { + return cli.GetClient().ResetClouduserPassword(args.NAME, args.PASSWORD) + }) + + type DomainListOptions struct { + } + + shellutils.R(&DomainListOptions{}, "domain-list", "List domains", func(cli *azure.SRegion, args *DomainListOptions) error { + domains, err := cli.GetClient().GetDomains() + if err != nil { + return err + } + printList(domains, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/google/google.go b/pkg/multicloud/google/google.go index 62a1006a13..dbc38f012f 100644 --- a/pkg/multicloud/google/google.go +++ b/pkg/multicloud/google/google.go @@ -51,6 +51,7 @@ const ( GOOGLE_BILLING_API_VERSION = "v1" GOOGLE_MONITOR_API_VERSION = "v3" GOOGLE_DBINSTANCE_API_VERSION = "v1beta4" + GOOGLE_IAM_API_VERSION = "v1" GOOGLE_MANAGER_DOMAIN = "https://cloudresourcemanager.googleapis.com" GOOGLE_COMPUTE_DOMAIN = "https://www.googleapis.com/compute" @@ -60,6 +61,7 @@ const ( GOOGLE_BILLING_DOMAIN = "https://cloudbilling.googleapis.com" GOOGLE_MONITOR_DOMAIN = "https://monitoring.googleapis.com" GOOGLE_DBINSTANCE_DOMAIN = "https://www.googleapis.com/sql" + GOOGLE_IAM_DOMAIN = "https://iam.googleapis.com" MAX_RETRY = 3 ) @@ -258,6 +260,42 @@ func (self *SGoogleClient) managerGet(resource string) (jsonutils.JSONObject, er return jsonRequest(self.client, "GET", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, nil, nil, self.debug) } +func (self *SGoogleClient) managerPost(resource string, params map[string]string, body jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return jsonRequest(self.client, "POST", GOOGLE_MANAGER_DOMAIN, GOOGLE_MANAGER_API_VERSION, resource, params, body, self.debug) +} + +func (self *SGoogleClient) iamList(resource string, params map[string]string) (jsonutils.JSONObject, error) { + return jsonRequest(self.client, "GET", GOOGLE_IAM_DOMAIN, GOOGLE_IAM_API_VERSION, resource, params, nil, self.debug) +} + +func (self *SGoogleClient) iamListAll(resource string, params map[string]string, retval interface{}) error { + if params == nil { + params = map[string]string{} + } + items := jsonutils.NewArray() + nextPageToken := "" + params["pageSize"] = "100" + for { + params["pageToken"] = nextPageToken + resp, err := self.iamList(resource, params) + if err != nil { + return errors.Wrap(err, "iamList") + } + if resp.Contains("roles") { + _items, err := resp.GetArray("roles") + if err != nil { + return errors.Wrap(err, "resp.GetArray") + } + items.Add(_items...) + } + nextPageToken, _ = resp.GetString("nextPageToken") + if len(nextPageToken) == 0 { + break + } + } + return items.Unmarshal(retval) +} + func (self *SGoogleClient) ecsListAll(resource string, params map[string]string, retval interface{}) error { if params == nil { params = map[string]string{} diff --git a/pkg/multicloud/google/iampolicy.go b/pkg/multicloud/google/iampolicy.go new file mode 100644 index 0000000000..b438baae29 --- /dev/null +++ b/pkg/multicloud/google/iampolicy.go @@ -0,0 +1,342 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2019 Yunion + +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SBinding struct { + Role string + Members []string +} + +type SIamPolicy struct { + client *SGoogleClient + Version int + Etag string + Bindings []SBinding +} + +func (self *SGoogleClient) GetIamPolicy() (*SIamPolicy, error) { + resource := fmt.Sprintf("projects/%s:getIamPolicy", self.projectId) + resp, err := self.managerPost(resource, nil, nil) + if err != nil { + return nil, errors.Wrap(err, "managerPost") + } + policy := &SIamPolicy{client: self} + err = resp.Unmarshal(policy) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + return policy, nil +} + +func (self *SGoogleClient) TestIam(permissions []string) ([]string, error) { + resource := fmt.Sprintf("projects/%s:testIamPermissions", self.projectId) + body := jsonutils.Marshal(map[string]interface{}{"permissions": permissions}) + resp, err := self.managerPost(resource, nil, body) + if err != nil { + return nil, errors.Wrap(err, "testIamPermissions") + } + ret := []string{} + err = resp.Unmarshal(&ret, "permissions") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return ret, nil +} + +func (self *SGoogleClient) IsSupportCloudId() bool { + permissions, err := self.TestIam([]string{"resourcemanager.projects.setIamPolicy"}) + if err != nil { + return false + } + return len(permissions) == 1 +} + +func (self *SGoogleClient) SetIamPlicy(policy *SIamPolicy) error { + resource := fmt.Sprintf("projects/%s:setIamPolicy", self.projectId) + body := jsonutils.Marshal(map[string]interface{}{"policy": map[string]interface{}{"bindings": policy.Bindings}}) + _, err := self.managerPost(resource, nil, body) + if err != nil { + return errors.Wrap(err, "managerPost") + } + return nil +} + +type SClouduserRole struct { + role string +} + +func (role *SClouduserRole) GetDescription() string { + return "" +} + +func (role *SClouduserRole) GetName() string { + return role.role +} + +func (role *SClouduserRole) GetPolicyType() string { + return role.role +} + +func (role *SClouduserRole) GetGlobalId() string { + return role.role +} + +type SClouduser struct { + policy *SIamPolicy + Name string + Roles []string +} + +func (self *SGoogleClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + roles, err := self.GetRoles() + if err != nil { + return nil, errors.Wrap(err, "GetRoles") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range roles { + ret = append(ret, &roles[i]) + } + return ret, nil +} + +type SRole struct { + Name string + Title string + Description string + Stage string + Etag string +} + +func (role *SRole) GetName() string { + return role.Title +} + +func (role *SRole) GetGlobalId() string { + return role.Name +} + +func (role *SRole) GetDescription() string { + return role.Description +} + +func (role *SRole) GetPolicyType() string { + return "System" +} + +// https://cloud.google.com/iam/docs/reference/rest/v1/roles/list +func (self *SGoogleClient) GetRoles() ([]SRole, error) { + roles := []SRole{} + err := self.iamListAll("roles", nil, &roles) + if err != nil { + return nil, errors.Wrap(err, "iamListAll.roles") + } + return roles, nil +} + +func (user *SClouduser) GetGlobalId() string { + return user.Name +} + +func (user *SClouduser) GetName() string { + return user.Name +} + +func (user *SClouduser) IsConsoleLogin() bool { + return true +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + ret := []cloudprovider.ICloudpolicy{} + for _, role := range user.Roles { + ret = append(ret, &SClouduserRole{role: role}) + } + return ret, nil +} + +var getUserName = func(user string) string { + if strings.HasSuffix(user, "gserviceaccount.com") { + return "serviceAccount:" + user + } + return "user:" + user +} + +func (policy *SIamPolicy) AttachPolicy(user string, roles []string) error { + for _, role := range roles { + find := false + for i := range policy.Bindings { + if policy.Bindings[i].Role == role { + if !utils.IsInStringArray(getUserName(user), policy.Bindings[i].Members) { + policy.Bindings[i].Members = append(policy.Bindings[i].Members, getUserName(user)) + find = true + } + } + } + if !find { + policy.Bindings = append(policy.Bindings, SBinding{Role: role, Members: []string{getUserName(user)}}) + } + } + return policy.client.SetIamPlicy(policy) +} + +func (user *SClouduser) AttachSystemPolicy(role string) error { + return user.policy.AttachPolicy(user.Name, []string{role}) +} + +func (policy *SIamPolicy) DetachPolicy(user, role string) error { + change := false + for i := range policy.Bindings { + if policy.Bindings[i].Role == role && utils.IsInStringArray(getUserName(user), policy.Bindings[i].Members) { + change = true + members := []string{} + for _, member := range policy.Bindings[i].Members { + if member != getUserName(user) { + members = append(members, member) + } + } + policy.Bindings[i].Members = members + } + } + if change { + return policy.client.SetIamPlicy(policy) + } + return nil +} + +func (client *SGoogleClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + policy, err := client.GetIamPolicy() + if err != nil { + return nil, errors.Wrap(err, "GetIamPolicy") + } + if len(conf.ExternalPolicyIds) == 0 { + return nil, fmt.Errorf("missing policy info") + } + err = policy.AttachPolicy(conf.Name, conf.ExternalPolicyIds) + if err != nil { + return nil, errors.Wrap(err, "policy.AttachPolicy") + } + return client.GetIClouduserByName(conf.Name) +} + +func (client *SGoogleClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + policy, err := client.GetIamPolicy() + if err != nil { + return nil, errors.Wrap(err, "GetIamPolicy") + } + users, err := policy.GetICloudusers() + if err != nil { + return nil, errors.Wrap(err, "policy.GetICloudusers") + } + for i := range users { + if users[i].GetName() == name { + return users[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (user *SClouduser) DetachSystemPolicy(role string) error { + return user.policy.DetachPolicy(user.Name, role) +} + +func (policy *SIamPolicy) DeleteUser(user string) error { + change := false + for i := range policy.Bindings { + if utils.IsInStringArray(getUserName(user), policy.Bindings[i].Members) { + change = true + members := []string{} + for _, member := range policy.Bindings[i].Members { + if member != getUserName(user) { + members = append(members, member) + } + } + policy.Bindings[i].Members = members + } + } + if change { + return policy.client.SetIamPlicy(policy) + } + return nil +} + +func (user *SClouduser) Delete() error { + return user.policy.DeleteUser(user.Name) +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return []cloudprovider.ICloudgroup{}, nil +} + +func (user *SClouduser) ResetPassword(password string) error { + return cloudprovider.ErrNotSupported +} + +func (client *SGoogleClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + policy, err := client.GetIamPolicy() + if err != nil { + return nil, errors.Wrap(err, "GetIamPolicy") + } + return policy.GetICloudusers() +} + +func (policy *SIamPolicy) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users := map[string]*SClouduser{} + for _, binding := range policy.Bindings { + for _, member := range binding.Members { + if strings.HasPrefix(member, "user:") { + user := strings.TrimPrefix(member, "user:") + if _, ok := users[user]; !ok { + users[user] = &SClouduser{Name: user, Roles: []string{}} + } + roles := users[user].Roles + if !utils.IsInStringArray(binding.Role, roles) { + roles = append(roles, binding.Role) + } + users[user].Roles = roles + } + } + } + cloudusers := []cloudprovider.IClouduser{} + for i := range users { + users[i].policy = policy + cloudusers = append(cloudusers, users[i]) + } + return cloudusers, nil +} diff --git a/pkg/multicloud/google/provider/provider.go b/pkg/multicloud/google/provider/provider.go index d3d50c6b32..c622715c2e 100644 --- a/pkg/multicloud/google/provider/provider.go +++ b/pkg/multicloud/google/provider/provider.go @@ -49,6 +49,30 @@ func (self *SGoogleProviderFactory) IsSupportPrepaidResources() bool { return false } +func (self *SGoogleProviderFactory) IsClouduserSupportPassword() bool { + return false +} + +func (factory *SGoogleProviderFactory) IsSupportClouduser() bool { + return true +} + +func (factory *SGoogleProviderFactory) IsClouduserNeedInitPolicy() bool { + return true +} + +func (factory *SGoogleProviderFactory) IsSupportResetClouduserPassword() bool { + return false +} + +func (factory *SGoogleProviderFactory) GetClouduserMinPolicyCount() int { + return 1 +} + +func (self *SGoogleProviderFactory) IsClouduserBelongCloudprovider() bool { + return true +} + func (self *SGoogleProviderFactory) NeedSyncSkuFromCloud() bool { return false } @@ -182,6 +206,14 @@ func (self *SGoogleProvider) GetAccountId() string { return self.client.GetAccountId() } +func (self *SGoogleProvider) GetIamLoginUlr() string { + return "https://console.cloud.google.com" +} + +func (self *SGoogleProvider) IsSupportCloudId() bool { + return self.client.IsSupportCloudId() +} + func (self *SGoogleProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } @@ -207,3 +239,27 @@ func (self *SGoogleProvider) GetStorageClasses(regionId string) []string { func (self *SGoogleProvider) GetCapabilities() []string { return self.client.GetCapabilities() } + +func (self *SGoogleProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SGoogleProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return []cloudprovider.ICloudgroup{}, nil +} + +func (self *SGoogleProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return nil, cloudprovider.ErrNotFound +} + +func (self *SGoogleProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetISystemCloudpolicies() +} + +func (self *SGoogleProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SGoogleProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} diff --git a/pkg/multicloud/google/shell/project.go b/pkg/multicloud/google/shell/project.go index 2ef5cc5712..b854339d7b 100644 --- a/pkg/multicloud/google/shell/project.go +++ b/pkg/multicloud/google/shell/project.go @@ -32,4 +32,81 @@ func init() { printList(projects, 0, 0, 0, nil) return nil }) + + type RoleShowOptions struct { + } + shellutils.R(&RoleShowOptions{}, "iam-policy-show", "Show project policy", func(cli *google.SRegion, args *RoleShowOptions) error { + policy, err := cli.GetClient().GetIamPolicy() + if err != nil { + return err + } + printObject(policy) + return nil + }) + + type ClouduserListOptions struct { + } + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List cloudusers", func(cli *google.SRegion, args *ClouduserListOptions) error { + policy, err := cli.GetClient().GetIamPolicy() + if err != nil { + return err + } + users, err := policy.GetICloudusers() + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type RoleListOptions struct { + } + + shellutils.R(&RoleListOptions{}, "role-list", "List roles", func(cli *google.SRegion, args *RoleListOptions) error { + roles, err := cli.GetClient().GetRoles() + if err != nil { + return err + } + printList(roles, 0, 0, 0, nil) + return nil + }) + + type ClouduserOptions struct { + USER string + ROLES []string + } + + shellutils.R(&ClouduserOptions{}, "cloud-user-add", "Add user to project", func(cli *google.SRegion, args *ClouduserOptions) error { + policy, err := cli.GetClient().GetIamPolicy() + if err != nil { + return err + } + return policy.AttachPolicy(args.USER, args.ROLES) + }) + + type ClouduserDetachRoleOptions struct { + USER string + ROLE string + } + + shellutils.R(&ClouduserDetachRoleOptions{}, "cloud-user-detach-role", "Detach role for clouduser", func(cli *google.SRegion, args *ClouduserDetachRoleOptions) error { + policy, err := cli.GetClient().GetIamPolicy() + if err != nil { + return err + } + return policy.DetachPolicy(args.USER, args.ROLE) + }) + + type ClouduserDeleteOptions struct { + USER string + } + + shellutils.R(&ClouduserDeleteOptions{}, "cloud-user-delete", "Delete clouduseruser from project", func(cli *google.SRegion, args *ClouduserDeleteOptions) error { + policy, err := cli.GetClient().GetIamPolicy() + if err != nil { + return err + } + return policy.DeleteUser(args.USER) + }) + } diff --git a/pkg/multicloud/huawei/client/client.go b/pkg/multicloud/huawei/client/client.go index df4d206006..c4c9b37119 100644 --- a/pkg/multicloud/huawei/client/client.go +++ b/pkg/multicloud/huawei/client/client.go @@ -84,6 +84,8 @@ type Client struct { CloudEye *modules.SCloudEyeManager Quotas *modules.SQuotaManager EnterpriseProjects *modules.SEnterpriseProjectManager + Roles *modules.SRoleManager + Groups *modules.SGroupManager } func (self *Client) SetHttpClient(httpClient *http.Client) { @@ -137,6 +139,8 @@ func (self *Client) SetHttpClient(httpClient *http.Client) { self.Traces.SetHttpClient(httpClient) self.CloudEye.SetHttpClient(httpClient) self.EnterpriseProjects.SetHttpClient(httpClient) + self.Roles.SetHttpClient(httpClient) + self.Groups.SetHttpClient(httpClient) } func (self *Client) InitWithOptions(regionId, domainId, projectId string, credential auth.Credential) error { @@ -198,6 +202,7 @@ func (self *Client) initManagers() { self.NovaSecurityGroups = modules.NewNovaSecurityGroupManager(self.regionId, self.projectId, self.signer, self.debug) self.Subnets = modules.NewSubnetManager(self.regionId, self.projectId, self.signer, self.debug) self.Users = modules.NewUserManager(self.signer, self.debug) + self.Users.SetDomainId(self.domainId) self.Interface = modules.NewInterfaceManager(self.regionId, self.projectId, self.signer, self.debug) self.Jobs = modules.NewJobManager(self.regionId, self.projectId, self.signer, self.debug) self.Balances = modules.NewBalanceManager(self.signer, self.debug) @@ -218,6 +223,10 @@ func (self *Client) initManagers() { self.Quotas = modules.NewQuotaManager(self.regionId, self.projectId, self.signer, self.debug) self.EnterpriseProjects = modules.NewEnterpriseProjectManager(self.regionId, self.projectId, self.signer, self.debug) self.EnterpriseProjects.SetDomainId(self.domainId) + self.Roles = modules.NewRoleManager(self.signer, self.debug) + self.Roles.SetDomainId(self.domainId) + self.Groups = modules.NewGroupManager(self.signer, self.debug) + self.Groups.SetDomainId(self.domainId) } self.init = true diff --git a/pkg/multicloud/huawei/client/modules/manager_resource.go b/pkg/multicloud/huawei/client/modules/manager_resource.go index db5a3d6f36..dbfdf9cbc6 100644 --- a/pkg/multicloud/huawei/client/modules/manager_resource.go +++ b/pkg/multicloud/huawei/client/modules/manager_resource.go @@ -209,6 +209,9 @@ func (self *SResourceManager) UpdateInContextWithSpec(ctx manager.IManagerContex if len(content) > 0 { request.SetContent([]byte(content)) } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } return self._do(request, responseKey) } @@ -231,6 +234,9 @@ func (self *SResourceManager) DeleteInContextWithSpec(ctx manager.IManagerContex if len(content) > 0 { request.SetContent([]byte(content)) } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } return self._do(request, responseKey) } diff --git a/pkg/multicloud/huawei/client/modules/mod_groups.go b/pkg/multicloud/huawei/client/modules/mod_groups.go new file mode 100644 index 0000000000..cc22811abe --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_groups.go @@ -0,0 +1,81 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" +) + +type SGroupManager struct { + SResourceManager +} + +func NewGroupManager(signer auth.Signer, debug bool) *SGroupManager { + return &SGroupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameIAM, + Region: "", + ProjectId: "", + version: "v3", + Keyword: "group", + KeywordPlural: "groups", + + ResourceKeyword: "groups", + }} +} + +func (manager *SGroupManager) ListRoles(domainId string, groupId string) (*responses.ListResult, error) { + if len(domainId) == 0 { + return nil, fmt.Errorf("missing domainId") + } + if len(groupId) == 0 { + return nil, fmt.Errorf("missing groupId") + } + manager.SetVersion(fmt.Sprintf("v3/domains/%s", domainId)) + return manager.ListInContextWithSpec(nil, fmt.Sprintf("%s/roles", groupId), nil, "roles") +} + +func (manager *SGroupManager) DeleteRole(domainId string, groupId, roleId string) error { + if len(domainId) == 0 { + return fmt.Errorf("missing domainId") + } + if len(groupId) == 0 { + return fmt.Errorf("missing groupId") + } + if len(roleId) == 0 { + return fmt.Errorf("missing roleId") + } + manager.SetVersion(fmt.Sprintf("v3/domains/%s", domainId)) + _, err := manager.DeleteInContextWithSpec(nil, groupId, fmt.Sprintf("roles/%s", roleId), nil, nil, "") + return err +} + +func (manager *SGroupManager) AddRole(domainId string, groupId, roleId string) error { + if len(domainId) == 0 { + return fmt.Errorf("missing domainId") + } + if len(groupId) == 0 { + return fmt.Errorf("missing groupId") + } + if len(roleId) == 0 { + return fmt.Errorf("missing roleId") + } + manager.SetVersion(fmt.Sprintf("v3/domains/%s", domainId)) + _, err := manager.UpdateInContextWithSpec(nil, groupId, fmt.Sprintf("roles/%s", roleId), nil, "") + return err +} diff --git a/pkg/multicloud/huawei/client/modules/mod_roles.go b/pkg/multicloud/huawei/client/modules/mod_roles.go new file mode 100644 index 0000000000..6defe96e18 --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_roles.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" +) + +type SRoleManager struct { + SResourceManager +} + +func NewRoleManager(signer auth.Signer, debug bool) *SRoleManager { + return &SRoleManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameIAM, + Region: "", + ProjectId: "", + version: "v3", + Keyword: "role", + KeywordPlural: "roles", + + ResourceKeyword: "roles", + }} +} diff --git a/pkg/multicloud/huawei/client/modules/mod_users.go b/pkg/multicloud/huawei/client/modules/mod_users.go index 22c1474d91..d2757c4902 100644 --- a/pkg/multicloud/huawei/client/modules/mod_users.go +++ b/pkg/multicloud/huawei/client/modules/mod_users.go @@ -15,7 +15,12 @@ package modules import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) type SUserManager struct { @@ -28,10 +33,35 @@ func NewUserManager(signer auth.Signer, debug bool) *SUserManager { ServiceName: ServiceNameIAM, Region: "", ProjectId: "", - version: "v3.0", + version: "v3.0/OS-USER", Keyword: "user", KeywordPlural: "users", - ResourceKeyword: "OS-USER/users", + ResourceKeyword: "users", }} } + +func (self *SUserManager) List(querys map[string]string) (*responses.ListResult, error) { + self.SetVersion("v3") + return self.SResourceManager.List(querys) +} + +func (self *SUserManager) Delete(id string) (jsonutils.JSONObject, error) { + self.SetVersion("v3") + return self.SResourceManager.Delete(id, nil) +} + +func (self *SUserManager) ResetPassword(id, password string) error { + params := map[string]interface{}{ + "user": map[string]string{ + "password": password, + }, + } + _, err := self.SResourceManager.Update(id, jsonutils.Marshal(params)) + return err +} + +func (self *SUserManager) ListGroups(userId string) (*responses.ListResult, error) { + self.SetVersion("v3") + return self.SResourceManager.ListInContextWithSpec(nil, fmt.Sprintf("%s/groups", userId), nil, "groups") +} diff --git a/pkg/multicloud/huawei/client/responses/responses.go b/pkg/multicloud/huawei/client/responses/responses.go index 166c1f603a..7bdae4aa35 100644 --- a/pkg/multicloud/huawei/client/responses/responses.go +++ b/pkg/multicloud/huawei/client/responses/responses.go @@ -22,10 +22,11 @@ import ( ) type ListResult struct { - Data []jsonutils.JSONObject - Total int - Limit int - Offset int + Data []jsonutils.JSONObject + Total int + Limit int + Offset int + NextLink string } func ListResult2JSONWithKey(result *ListResult, key string) jsonutils.JSONObject { diff --git a/pkg/multicloud/huawei/cloudgroup.go b/pkg/multicloud/huawei/cloudgroup.go new file mode 100644 index 0000000000..3b876d9a6c --- /dev/null +++ b/pkg/multicloud/huawei/cloudgroup.go @@ -0,0 +1,257 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SCloudgroup struct { + client *SHuaweiClient + Name string + Description string + Id string + CreateTime string +} + +func (group *SCloudgroup) GetName() string { + return group.Name +} + +func (group *SCloudgroup) GetDescription() string { + return group.Description +} + +func (group *SCloudgroup) GetGlobalId() string { + return group.Id +} + +func (group *SCloudgroup) Delete() error { + return group.client.DeleteGroup(group.Id) +} + +func (group *SCloudgroup) AddUser(name string) error { + user, err := group.client.GetIClouduserByName(name) + if err != nil { + return errors.Wrap(err, "GetIClouduserByName") + } + return group.client.AddUserToGroup(group.Id, user.GetGlobalId()) +} + +func (group *SCloudgroup) RemoveUser(name string) error { + user, err := group.client.GetIClouduserByName(name) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + return errors.Wrapf(err, "GetIClouduserByName(%s)", name) + } + return group.client.RemoveUserFromGroup(group.Id, user.GetGlobalId()) +} + +func (group *SCloudgroup) DetachSystemPolicy(roleId string) error { + return group.client.DetachGroupRole(group.Id, roleId) +} + +func (group *SCloudgroup) AttachSystemPolicy(roleId string) error { + return group.client.AttachGroupRole(group.Id, roleId) +} + +func (group *SCloudgroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + roles, err := group.client.GetGroupRoles(group.Id) + if err != nil { + return nil, errors.Wrap(err, "GetGroupRoles") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range roles { + ret = append(ret, &roles[i]) + } + return ret, nil +} + +func (group *SCloudgroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := group.client.GetGroupUsers(group.Id) + if err != nil { + return nil, err + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = group.client + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (self *SHuaweiClient) GetGroups(domainId, name string) ([]SCloudgroup, error) { + params := map[string]string{} + if len(domainId) > 0 { + params["domain_id"] = self.ownerId + } + if len(name) > 0 { + params["name"] = name + } + + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + groups := []SCloudgroup{} + err = doListAllWithNextLink(client.Groups.List, params, &groups) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return groups, nil +} + +func (self *SHuaweiClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := self.GetGroups("", "") + if err != nil { + return nil, errors.Wrap(err, "GetGroup") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + if groups[i].Name != "admin" { + groups[i].client = self + ret = append(ret, &groups[i]) + } + } + return ret, nil +} + +func (self *SHuaweiClient) GetGroupUsers(groupId string) ([]SClouduser, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + resp, err := client.Groups.ListInContextWithSpec(nil, fmt.Sprintf("%s/users", groupId), nil, "users") + if err != nil { + return nil, errors.Wrap(err, "") + } + users := []SClouduser{} + err = jsonutils.Update(&users, resp.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return users, nil +} + +func (self *SHuaweiClient) GetGroupRoles(groupId string) ([]SRole, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + resp, err := client.Groups.ListRoles(self.ownerId, groupId) + if err != nil { + return nil, errors.Wrap(err, "ListRoles") + } + roles := []SRole{} + err = jsonutils.Update(&roles, resp.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return roles, nil +} + +func (self *SHuaweiClient) CreateGroup(name, desc string) (*SCloudgroup, error) { + params := map[string]string{ + "name": name, + } + if len(desc) > 0 { + params["description"] = desc + } + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + group := SCloudgroup{client: self} + err = DoCreate(client.Groups.Create, jsonutils.Marshal(map[string]interface{}{"group": params}), &group) + if err != nil { + return nil, errors.Wrap(err, "DoCreate") + } + return &group, nil +} + +func (self *SHuaweiClient) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + group, err := self.CreateGroup(name, desc) + if err != nil { + return nil, errors.Wrap(err, "CreateGroup") + } + return group, nil +} + +func (self *SHuaweiClient) DeleteGroup(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + return DoDeleteWithSpec(client.Groups.DeleteInContextWithSpec, nil, id, "", nil, nil) +} + +func (self *SHuaweiClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + groups, err := self.GetGroups(self.ownerId, name) + if err != nil { + return nil, errors.Wrap(err, "GetGroups") + } + if len(groups) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(groups) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + groups[0].client = self + return &groups[0], nil +} + +func (self *SHuaweiClient) AddUserToGroup(groupId, userId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.Groups.UpdateInContextWithSpec(nil, groupId, fmt.Sprintf("users/%s", userId), nil, "") + return err +} + +func (self *SHuaweiClient) RemoveUserFromGroup(groupId, userId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.Groups.DeleteInContextWithSpec(nil, groupId, fmt.Sprintf("users/%s", userId), nil, nil, "") + return err +} + +func (self *SHuaweiClient) DetachGroupRole(groupId, roleId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + return client.Groups.DeleteRole(self.ownerId, groupId, roleId) +} + +func (self *SHuaweiClient) AttachGroupRole(groupId, roleId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + return client.Groups.AddRole(self.ownerId, groupId, roleId) +} diff --git a/pkg/multicloud/huawei/clouduser.go b/pkg/multicloud/huawei/clouduser.go new file mode 100644 index 0000000000..befcbd5b5f --- /dev/null +++ b/pkg/multicloud/huawei/clouduser.go @@ -0,0 +1,196 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLink struct { + Next string + Previous string + Self string +} + +type SClouduser struct { + client *SHuaweiClient + + Description string + DomainId string + Enabled bool + ForceResetPwd bool + Id string + LastProjectId string + Links SLink + Name string + PasswordExpiresAt string + PwdStatus bool +} + +func (user *SClouduser) GetGlobalId() string { + return user.Id +} + +func (user *SClouduser) GetName() string { + return user.Name +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return []cloudprovider.ICloudpolicy{}, nil +} + +func (user *SClouduser) AttachSystemPolicy(policyType string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) DetachSystemPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := user.client.ListUserGroups(user.Id) + if err != nil { + return nil, errors.Wrap(err, "Users.ListGroups") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = user.client + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (user *SClouduser) Delete() error { + return user.client.DeleteClouduser(user.Id) +} + +func (user *SClouduser) IsConsoleLogin() bool { + return user.Enabled == true +} + +func (user *SClouduser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.Id, password) +} + +func (self *SHuaweiClient) DeleteClouduser(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.Users.Delete(id) + return err +} + +func (self *SHuaweiClient) ListUserGroups(userId string) ([]SCloudgroup, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + result, err := client.Users.ListGroups(userId) + if err != nil { + return nil, errors.Wrap(err, "Users.ListGroups") + } + groups := []SCloudgroup{} + err = jsonutils.Update(&groups, result.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return groups, nil +} + +func (self *SHuaweiClient) GetCloudusers(name string) ([]SClouduser, error) { + params := map[string]string{} + if len(name) > 0 { + params["name"] = name + } + users := []SClouduser{} + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + err = doListAllWithOffset(client.Users.List, params, &users) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return users, nil +} + +func (self *SHuaweiClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := self.GetCloudusers("") + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + iUsers := []cloudprovider.IClouduser{} + for i := range users { + if users[i].Name != self.ownerName { + users[i].client = self + iUsers = append(iUsers, &users[i]) + } + } + return iUsers, nil +} + +func (self *SHuaweiClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + users, err := self.GetCloudusers(name) + if err != nil { + return nil, errors.Wrapf(err, "GetCloudusers(%s)", name) + } + if len(users) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(users) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + users[0].client = self + return &users[0], nil +} + +func (self *SHuaweiClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.CreateClouduser(conf.Name, conf.Password, conf.Desc) +} + +func (self *SHuaweiClient) CreateClouduser(name, password, desc string) (*SClouduser, error) { + params := map[string]string{ + "name": name, + "domain_id": self.ownerId, + } + if len(password) > 0 { + params["password"] = password + } + if len(desc) > 0 { + params["description"] = desc + } + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + user := SClouduser{client: self} + err = DoCreate(client.Users.Create, jsonutils.Marshal(map[string]interface{}{"user": params}), &user) + if err != nil { + return nil, errors.Wrap(err, "DoCreate") + } + return &user, nil +} + +func (self *SHuaweiClient) ResetClouduserPassword(id, password string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + return client.Users.ResetPassword(id, password) +} diff --git a/pkg/multicloud/huawei/huawei.go b/pkg/multicloud/huawei/huawei.go index 338649bbbf..a5807da46a 100644 --- a/pkg/multicloud/huawei/huawei.go +++ b/pkg/multicloud/huawei/huawei.go @@ -86,7 +86,8 @@ type SHuaweiClient struct { isMainProject bool // whether the project is the main project in the region - ownerId string + ownerId string + ownerName string iregions []cloudprovider.ICloudRegion iBuckets []cloudprovider.ICloudBucket @@ -121,7 +122,7 @@ func (self *SHuaweiClient) init() error { return errors.Wrap(err, "fetchOwner") } if self.debug { - log.Debugf("OwnerId: %s", self.ownerId) + log.Debugf("OwnerId: %s name: %s", self.ownerId, self.ownerName) } return nil } @@ -314,6 +315,10 @@ func (client *SHuaweiClient) GetAccountId() string { return client.ownerId } +func (client *SHuaweiClient) GetIamLoginUrl() string { + return fmt.Sprintf("https://auth.huaweicloud.com/authui/login.html#/login?account=%s", client.ownerName) +} + func (self *SHuaweiClient) GetIRegions() []cloudprovider.ICloudRegion { return self.iregions } @@ -508,6 +513,7 @@ func (self *SHuaweiClient) GetOwnerId() (string, error) { type user struct { DomainId string `json:"domain_id"` + Name string `json:"name"` } ret := &user{} @@ -515,7 +521,7 @@ func (self *SHuaweiClient) GetOwnerId() (string, error) { if err != nil { return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.DoGet") } - + self.ownerName = ret.Name return ret.DomainId, nil } diff --git a/pkg/multicloud/huawei/provider/provider.go b/pkg/multicloud/huawei/provider/provider.go index ddf632e339..b4b0a704ef 100644 --- a/pkg/multicloud/huawei/provider/provider.go +++ b/pkg/multicloud/huawei/provider/provider.go @@ -52,6 +52,18 @@ func (self *SHuaweiProviderFactory) GetMaxCloudEventKeepDays() int { return 7 } +func (self *SHuaweiProviderFactory) IsSupportClouduser() bool { + return true +} + +func (self *SHuaweiProviderFactory) IsSupportClouduserPolicy() bool { + return false +} + +func (self *SHuaweiProviderFactory) IsSupportCreateCloudgroup() bool { + return true +} + func (self *SHuaweiProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { output := cloudprovider.SCloudaccount{} if len(input.AccessKeyId) == 0 { @@ -176,6 +188,14 @@ func (self *SHuaweiProvider) GetAccountId() string { return self.client.GetAccountId() } +func (self *SHuaweiProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SHuaweiProvider) IsSupportCloudId() bool { + return cloudprovider.IsSupportCloudId(self) +} + func (self *SHuaweiProvider) GetCloudRegionExternalIdPrefix() string { return self.client.GetCloudRegionExternalIdPrefix() } @@ -197,3 +217,31 @@ func (self *SHuaweiProvider) GetStorageClasses(regionId string) []string { func (self *SHuaweiProvider) GetCapabilities() []string { return self.client.GetCapabilities() } + +func (self *SHuaweiProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SHuaweiProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SHuaweiProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SHuaweiProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} + +func (self *SHuaweiProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SHuaweiProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetICloudpolicies() +} + +func (self *SHuaweiProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} diff --git a/pkg/multicloud/huawei/roles.go b/pkg/multicloud/huawei/roles.go new file mode 100644 index 0000000000..a274918ebc --- /dev/null +++ b/pkg/multicloud/huawei/roles.go @@ -0,0 +1,85 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SRole struct { + DomainId string + Flag string + DescriptionCn string + Catelog string + Description string + Id string + DisplayName string + Type string + UpdatedTime string + CreatedTime string + Links SLink +} + +func (role *SRole) GetName() string { + return role.DisplayName +} + +func (role *SRole) GetDescription() string { + return role.DescriptionCn +} + +func (role *SRole) GetPolicyType() string { + return "System" +} + +func (role *SRole) GetGlobalId() string { + return role.Id +} + +func (self *SHuaweiClient) GetICloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + roles, err := self.GetRoles("", "") + if err != nil { + return nil, errors.Wrap(err, "GetRoles") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range roles { + ret = append(ret, &roles[i]) + } + return ret, nil +} + +func (self *SHuaweiClient) GetRoles(domainId, name string) ([]SRole, error) { + params := map[string]string{} + if len(domainId) > 0 { + params["domain_id"] = self.ownerId + } + if len(name) > 0 { + params["name"] = name + } + + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + roles := []SRole{} + err = doListAllWithNextLink(client.Roles.List, params, &roles) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return roles, nil +} diff --git a/pkg/multicloud/huawei/shell/cloudgroup.go b/pkg/multicloud/huawei/shell/cloudgroup.go new file mode 100644 index 0000000000..ca2dfafec6 --- /dev/null +++ b/pkg/multicloud/huawei/shell/cloudgroup.go @@ -0,0 +1,84 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CloudgroupListOptions struct { + DomainId string + Name string + } + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List cloudgroups", func(cli *huawei.SRegion, args *CloudgroupListOptions) error { + groups, err := cli.GetClient().GetGroups(args.DomainId, args.Name) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type CloudgroupIdOptions struct { + ID string + } + + shellutils.R(&CloudgroupIdOptions{}, "cloud-group-delete", "Delete cloudgroup", func(cli *huawei.SRegion, args *CloudgroupIdOptions) error { + return cli.GetClient().DeleteGroup(args.ID) + }) + + type CloudgroupCreateOptions struct { + NAME string + Desc string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create cloudgroup", func(cli *huawei.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Desc) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type GroupRoleListOptions struct { + GROUP_ID string + } + + shellutils.R(&GroupRoleListOptions{}, "group-role-list", "List role", func(cli *huawei.SRegion, args *GroupRoleListOptions) error { + roles, err := cli.GetClient().GetGroupRoles(args.GROUP_ID) + if err != nil { + return err + } + printList(roles, 0, 0, 0, nil) + return nil + }) + + type GroupUserListOptions struct { + GROUP_ID string + } + + shellutils.R(&GroupUserListOptions{}, "group-user-list", "List user", func(cli *huawei.SRegion, args *GroupUserListOptions) error { + users, err := cli.GetClient().GetGroupUsers(args.GROUP_ID) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/huawei/shell/clouduser.go b/pkg/multicloud/huawei/shell/clouduser.go new file mode 100644 index 0000000000..e3ee3e7355 --- /dev/null +++ b/pkg/multicloud/huawei/shell/clouduser.go @@ -0,0 +1,90 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserListOptions struct { + Name string + } + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List cloudusers", func(cli *huawei.SRegion, args *ClouduserListOptions) error { + users, err := cli.GetClient().GetCloudusers(args.Name) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type ClouduserIdOptions struct { + ID string + } + + shellutils.R(&ClouduserIdOptions{}, "cloud-user-delete", "Delete clouduser", func(cli *huawei.SRegion, args *ClouduserIdOptions) error { + return cli.GetClient().DeleteClouduser(args.ID) + }) + + shellutils.R(&ClouduserIdOptions{}, "cloud-user-group-list", "List clouduser groups", func(cli *huawei.SRegion, args *ClouduserIdOptions) error { + groups, err := cli.GetClient().ListUserGroups(args.ID) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type ClouduserCreateOptions struct { + NAME string + Password string + Desc string + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create clouduser", func(cli *huawei.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateClouduser(args.NAME, args.Password, args.Desc) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type RoleListOptions struct { + DomainId string + Name string + } + + shellutils.R(&RoleListOptions{}, "role-list", "List role", func(cli *huawei.SRegion, args *RoleListOptions) error { + roles, err := cli.GetClient().GetRoles(args.DomainId, args.Name) + if err != nil { + return err + } + printList(roles, 0, 0, 0, nil) + return nil + }) + + type ClouduserResetPassword struct { + ID string + PASSWORD string + } + + shellutils.R(&ClouduserResetPassword{}, "cloud-user-reset-password", "Reset clouduser password", func(cli *huawei.SRegion, args *ClouduserResetPassword) error { + return cli.GetClient().ResetClouduserPassword(args.ID, args.PASSWORD) + }) + +} diff --git a/pkg/multicloud/huawei/utils.go b/pkg/multicloud/huawei/utils.go index 09a5923c9f..e3eed756f0 100644 --- a/pkg/multicloud/huawei/utils.go +++ b/pkg/multicloud/huawei/utils.go @@ -21,6 +21,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -88,6 +89,24 @@ func doListAllWithPagerOffset(doList listFunc, queries map[string]string, result return nil } +func doListAllWithNextLink(doList listFunc, querys map[string]string, result interface{}) error { + values := []jsonutils.JSONObject{} + for { + ret, err := doList(querys) + if err != nil { + return errors.Wrap(err, "doList") + } + values = append(values, ret.Data...) + if len(ret.NextLink) == 0 || ret.NextLink == "null" { + break + } + } + if result != nil { + return jsonutils.Update(result, values) + } + return nil +} + func doListAllWithOffset(doList listFunc, queries map[string]string, result interface{}) error { startIndex := 0 resultValue := reflect.Indirect(reflect.ValueOf(result)) diff --git a/pkg/multicloud/qcloud/cloudgroup.go b/pkg/multicloud/qcloud/cloudgroup.go new file mode 100644 index 0000000000..fdac4fcb1d --- /dev/null +++ b/pkg/multicloud/qcloud/cloudgroup.go @@ -0,0 +1,318 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qcloud + +import ( + "fmt" + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SCloudgroup struct { + client *SQcloudClient + + GroupId int + GroupName string + CreateTime time.Time +} + +func (self *SCloudgroup) GetGlobalId() string { + return fmt.Sprintf("%d", self.GroupId) +} + +func (self *SCloudgroup) GetName() string { + return self.GroupName +} + +func (self *SCloudgroup) GetDescription() string { + return "" +} + +func (self *SCloudgroup) Delete() error { + return self.client.DeleteGroup(self.GroupId) +} + +func (self *SCloudgroup) AddUser(name string) error { + user, err := self.client.GetIClouduserByName(name) + if err != nil { + return errors.Wrapf(err, "GetIClouduserByName(%s)", name) + } + _user := user.(*SClouduser) + return self.client.AddUserToGroup(self.GroupId, int(_user.Uid)) +} + +func (self *SCloudgroup) RemoveUser(name string) error { + user, err := self.client.GetIClouduserByName(name) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + return errors.Wrapf(err, "GetIClouduserByName(%s)", name) + } + _user := user.(*SClouduser) + return self.client.RemoveUserFromGroup(self.GroupId, int(_user.Uid)) +} + +func (self *SCloudgroup) AttachSystemPolicy(policyId string) error { + return self.client.AttachGroupPolicy(self.GroupId, policyId) +} + +func (self *SCloudgroup) DetachSystemPolicy(policyId string) error { + return self.client.DetachGroupPolicy(self.GroupId, policyId) +} + +func (self *SCloudgroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SClouduserPolicy{} + page := 1 + for { + part, total, err := self.client.ListAttachedGroupPolicies(self.GroupId, page, 50) + if err != nil { + return nil, errors.Wrap(err, "ListAttachedGroupPolicies") + } + policies = append(policies, part...) + if len(policies) >= total { + break + } + page += 1 + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == "QCS" { + policies[i].client = self.client + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (self *SCloudgroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users := []SClouduser{} + page := 1 + for { + part, total, err := self.client.ListGroupUsers(self.GroupId, page, 50) + if err != nil { + return nil, errors.Wrap(err, "ListGroupUsers") + } + users = append(users, part...) + if len(users) >= total { + break + } + page += 1 + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = self.client + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (self *SQcloudClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups := []SCloudgroup{} + page := 1 + for { + part, total, err := self.ListGroups("", page, 50) + if err != nil { + return nil, errors.Wrap(err, "ListGroups") + } + groups = append(groups, part...) + if len(groups) >= total { + break + } + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = self + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (self *SQcloudClient) ListGroups(keyword string, page int, rp int) ([]SCloudgroup, int, error) { + if page < 1 { + page = 1 + } + if rp <= 0 || rp > 50 { + rp = 50 + } + params := map[string]string{ + "Page": fmt.Sprintf("%d", page), + "Rp": fmt.Sprintf("%d", rp), + } + if len(keyword) > 0 { + params["keyword"] = keyword + } + + resp, err := self.camRequest("ListGroups", params) + if err != nil { + return nil, 0, errors.Wrap(err, "camRequest.ListGroups") + } + groups := []SCloudgroup{} + err = resp.Unmarshal(&groups, "GroupInfo") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalNum") + return groups, int(total), nil +} + +func (self *SQcloudClient) CreateGroup(name string, remark string) (*SCloudgroup, error) { + params := map[string]string{ + "GroupName": name, + } + if len(remark) > 0 { + params["Remark"] = remark + } + resp, err := self.camRequest("CreateGroup", params) + if err != nil { + return nil, errors.Wrap(err, "camRequest.CreateGroup") + } + groupId, _ := resp.Float("GroupId") + return self.GetGroup(int(groupId)) +} + +func (self *SQcloudClient) GetGroup(groupId int) (*SCloudgroup, error) { + params := map[string]string{ + "GroupId": fmt.Sprintf("%d", groupId), + } + resp, err := self.camRequest("GetGroup", params) + if err != nil { + return nil, errors.Wrap(err, "camRequest.GetGroup") + } + group := &SCloudgroup{client: self} + err = resp.Unmarshal(group) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return group, nil +} + +func (self *SQcloudClient) DeleteGroup(id int) error { + params := map[string]string{ + "GroupId": fmt.Sprintf("%d", id), + } + _, err := self.camRequest("DeleteGroup", params) + return err +} + +func (self *SQcloudClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + groups, err := self.GetICloudgroups() + if err != nil { + return nil, errors.Wrap(err, "GetICloudgroups") + } + for i := range groups { + if groups[i].GetName() == name { + return groups[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SQcloudClient) ListAttachedGroupPolicies(groupId int, page int, rp int) ([]SClouduserPolicy, int, error) { + if page < 1 { + page = 1 + } + if rp <= 0 || rp > 50 { + rp = 50 + } + params := map[string]string{ + "TargetGroupId": fmt.Sprintf("%d", groupId), + "Page": fmt.Sprintf("%d", page), + "Rp": fmt.Sprintf("%d", rp), + } + resp, err := self.camRequest("ListAttachedGroupPolicies", params) + if err != nil { + return nil, 0, errors.Wrap(err, "iamRequest.ListAttachedGroupPolicies") + } + policies := []SClouduserPolicy{} + err = resp.Unmarshal(&policies, "List") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalNum") + return policies, int(total), nil +} + +func (self *SQcloudClient) ListGroupUsers(groupId int, page int, rp int) ([]SClouduser, int, error) { + if page < 1 { + page = 1 + } + if rp <= 0 || rp > 50 { + rp = 50 + } + params := map[string]string{ + "GroupId": fmt.Sprintf("%d", groupId), + "Page": fmt.Sprintf("%d", page), + "Rp": fmt.Sprintf("%d", rp), + } + resp, err := self.camRequest("ListUsersForGroup", params) + if err != nil { + return nil, 0, errors.Wrap(err, "iamRequest.ListUserForGroup") + } + users := []SClouduser{} + err = resp.Unmarshal(&users, "UserInfo") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalNum") + return users, int(total), nil +} + +func (self *SQcloudClient) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + group, err := self.CreateGroup(name, desc) + if err != nil { + return nil, errors.Wrap(err, "CreateGroup") + } + return group, nil +} + +func (self *SQcloudClient) RemoveUserFromGroup(groupId, userId int) error { + params := map[string]string{ + "Info.0.Uid": fmt.Sprintf("%d", userId), + "Info.0.GroupId": fmt.Sprintf("%d", groupId), + } + _, err := self.camRequest("RemoveUserFromGroup", params) + return err +} + +func (self *SQcloudClient) AddUserToGroup(groupId, userId int) error { + params := map[string]string{ + "Info.0.Uid": fmt.Sprintf("%d", userId), + "Info.0.GroupId": fmt.Sprintf("%d", groupId), + } + _, err := self.camRequest("AddUserToGroup", params) + return err +} + +func (self *SQcloudClient) AttachGroupPolicy(groupId int, policyId string) error { + params := map[string]string{ + "PolicyId": policyId, + "AttachGroupId": fmt.Sprintf("%d", groupId), + } + _, err := self.camRequest("AttachGroupPolicy", params) + return err +} + +func (self *SQcloudClient) DetachGroupPolicy(groupId int, policyId string) error { + params := map[string]string{ + "PolicyId": policyId, + "DetachGroupId": fmt.Sprintf("%d", groupId), + } + _, err := self.camRequest("DetachGroupPolicy", params) + return err +} diff --git a/pkg/multicloud/qcloud/clouduser.go b/pkg/multicloud/qcloud/clouduser.go new file mode 100644 index 0000000000..7365718739 --- /dev/null +++ b/pkg/multicloud/qcloud/clouduser.go @@ -0,0 +1,382 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package qcloud + +import ( + "fmt" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SClouduser struct { + client *SQcloudClient + Uin int64 + Name string + Uid int64 + Remark string + ConsoleLogin int + CountryCode string + Email string +} + +func (user *SClouduser) GetGlobalId() string { + return fmt.Sprintf("%d", user.Uin) +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SClouduserPolicy{} + page := 1 + for { + part, total, err := user.client.ListAttachedUserPolicies(user.GetGlobalId(), page, 50) + if err != nil { + return nil, errors.Wrap(err, "GetClouduserPolicy") + } + policies = append(policies, part...) + if len(policies) >= total { + break + } + page += 1 + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + if policies[i].PolicyType == "QCS" { + policies[i].client = user.client + ret = append(ret, &policies[i]) + } + } + return ret, nil +} + +func (self *SQcloudClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + policies := []SClouduserPolicy{} + i := 1 + for { + part, total, err := self.ListPolicies("", "QCS", i, 100) + if err != nil { + return nil, errors.Wrap(err, "GetPolicies") + } + policies = append(policies, part...) + if len(policies) >= total { + break + } + i += 1 + } + ret := []cloudprovider.ICloudpolicy{} + for i := range policies { + policies[i].client = self + ret = append(ret, &policies[i]) + } + return ret, nil +} + +func (user *SClouduser) AttachSystemPolicy(policyId string) error { + return user.client.AttachPolicy(user.GetGlobalId(), policyId) +} + +func (user *SClouduser) DetachSystemPolicy(policyId string) error { + return user.client.DetachPolicy(user.GetGlobalId(), policyId) +} + +func (user *SClouduser) IsConsoleLogin() bool { + return user.ConsoleLogin == 1 +} + +func (user *SClouduser) Delete() error { + return user.client.DeleteUser(user.Name) +} + +func (user *SClouduser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.Name, password) +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups := []SCloudgroup{} + page := 1 + for { + part, total, err := user.client.ListGroupsForUser(fmt.Sprintf("%d", user.Uin), page, 50) + if err != nil { + return nil, errors.Wrap(err, "ListGroupsForUser") + } + groups = append(groups, part...) + if len(groups) >= total { + break + } + page += 1 + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = user.client + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (user *SClouduser) GetName() string { + return user.Name +} + +func (self *SQcloudClient) DeleteUser(name string) error { + params := map[string]string{ + "Name": name, + "Force": "1", + } + _, err := self.camRequest("DeleteUser", params) + return err +} + +func (self *SQcloudClient) ListUsers() ([]SClouduser, error) { + resp, err := self.camRequest("ListUsers", nil) + if err != nil { + return nil, errors.Wrap(err, "camRequest.ListUsers") + } + users := []SClouduser{} + err = resp.Unmarshal(&users, "Data") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return users, nil +} + +func (self *SQcloudClient) ListGroupsForUser(uin string, rp, page int) ([]SCloudgroup, int, error) { + if page < 1 { + page = 1 + } + if rp <= 0 || rp > 50 { + rp = 50 + } + params := map[string]string{ + "SubUin": uin, + "Page": fmt.Sprintf("%d", page), + "Rp": fmt.Sprintf("%d", rp), + } + resp, err := self.camRequest("ListGroupsForUser", params) + if err != nil { + return nil, 0, errors.Wrap(err, "camRequest.ListGroupsForUser") + } + groups := []SCloudgroup{} + err = resp.Unmarshal(&groups, "GroupInfo") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalNum") + return groups, int(total), nil +} + +func (self *SQcloudClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := self.ListUsers() + if err != nil { + return nil, errors.Wrap(err, "ListUsers") + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = self + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (self *SQcloudClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.GetClouduser(name) +} + +func (self *SQcloudClient) GetClouduser(name string) (*SClouduser, error) { + params := map[string]string{ + "Name": name, + } + resp, err := self.camRequest("GetUser", params) + if err != nil { + return nil, errors.Wrap(err, "camRequest.GetUser") + } + user := &SClouduser{client: self} + err = resp.Unmarshal(user) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return user, nil +} + +func (self *SQcloudClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + user, err := self.CreateClouduser(conf.Name, conf.Password, conf.Desc, conf.IsConsoleLogin) + if err != nil { + return nil, errors.Wrap(err, "CreateClouduser") + } + for _, policyId := range conf.ExternalPolicyIds { + err = user.client.AttachPolicy(fmt.Sprintf("%d", user.Uin), policyId) + if err != nil { + log.Errorf("attach policy %s for user %s error: %v", policyId, conf.Name, err) + } + } + return user, nil +} + +func (self *SQcloudClient) CreateClouduser(name, password, desc string, consoleLogin bool) (*SClouduser, error) { + params := map[string]string{ + "Name": name, + "Remark": desc, + "ConsoleLogin": "0", + } + if len(password) > 0 { + params["Password"] = password + } + if consoleLogin { + params["ConsoleLogin"] = "1" + } + resp, err := self.camRequest("AddUser", params) + if err != nil { + return nil, errors.Wrap(err, "camRequest.AddUser") + } + user := &SClouduser{client: self} + err = resp.Unmarshal(user) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return user, nil +} + +type SClouduserPolicy struct { + client *SQcloudClient + PolicyId int64 + PolicyName string + AddTime time.Time + CreateMode string + PolicyType string + Description string +} + +func (policy *SClouduserPolicy) GetGlobalId() string { + return fmt.Sprintf("%d", policy.PolicyId) +} +func (policy *SClouduserPolicy) GetName() string { + return policy.PolicyName +} + +func (policy *SClouduserPolicy) GetPolicyType() string { + return policy.PolicyName +} + +func (policy *SClouduserPolicy) GetDescription() string { + p, err := policy.client.GetPolicy(policy.GetGlobalId()) + if err != nil { + return p.Description + } + return "" +} + +func (self *SQcloudClient) ListAttachedUserPolicies(uin string, page int, rp int) ([]SClouduserPolicy, int, error) { + if page < 1 { + page = 1 + } + if rp <= 0 || rp > 50 { + rp = 50 + } + params := map[string]string{ + "TargetUin": uin, + "Page": fmt.Sprintf("%d", page), + "Rp": fmt.Sprintf("%d", rp), + } + resp, err := self.camRequest("ListAttachedUserPolicies", params) + if err != nil { + return nil, 0, errors.Wrap(err, "camRequest.ListAttachedUserPolicies") + } + policies := []SClouduserPolicy{} + err = resp.Unmarshal(&policies, "List") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalNum") + return policies, int(total), nil +} + +func (self *SQcloudClient) AttachPolicy(uin, policyId string) error { + params := map[string]string{ + "AttachUin": uin, + "PolicyId": policyId, + } + _, err := self.camRequest("AttachUserPolicy", params) + return err +} + +func (self *SQcloudClient) DetachPolicy(uin, policyId string) error { + params := map[string]string{ + "DetachUin": uin, + "PolicyId": policyId, + } + _, err := self.camRequest("DetachUserPolicy", params) + return err +} + +// https://cloud.tencent.com/document/api/598/34570 +func (self *SQcloudClient) ListPolicies(keyword, scope string, page int, rp int) ([]SClouduserPolicy, int, error) { + if page < 1 { + page = 1 + } + if rp <= 0 || rp > 50 { + rp = 50 + } + params := map[string]string{ + "Page": fmt.Sprintf("%d", page), + "Rp": fmt.Sprintf("%d", rp), + } + if len(scope) > 0 { + params["Scope"] = scope + } + if len(keyword) > 0 { + params["Keyword"] = keyword + } + resp, err := self.camRequest("ListPolicies", params) + if err != nil { + return nil, 0, errors.Wrap(err, "camRequest.ListPolicies") + } + policies := []SClouduserPolicy{} + err = resp.Unmarshal(&policies, "List") + if err != nil { + return nil, 0, errors.Wrap(err, "resp.Unmarshal") + } + total, _ := resp.Int("TotalNum") + return policies, int(total), nil +} + +func (self *SQcloudClient) GetPolicy(policyId string) (*SClouduserPolicy, error) { + params := map[string]string{ + "PolicyId": policyId, + } + resp, err := self.camRequest("GetPolicy", params) + if err != nil { + return nil, errors.Wrap(err, "GetPolicy") + } + policy := SClouduserPolicy{} + err = resp.Unmarshal(&policy) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return &policy, nil +} + +func (self *SQcloudClient) ResetClouduserPassword(name, password string) error { + params := map[string]string{ + "Name": name, + "ConsoleLogin": "1", + "Password": password, + } + _, err := self.camRequest("UpdateUser", params) + if err != nil { + return errors.Wrap(err, "UpdateUser") + } + return nil +} diff --git a/pkg/multicloud/qcloud/provider/provider.go b/pkg/multicloud/qcloud/provider/provider.go index da602767c1..5143030bd9 100644 --- a/pkg/multicloud/qcloud/provider/provider.go +++ b/pkg/multicloud/qcloud/provider/provider.go @@ -53,6 +53,14 @@ func (self *SQcloudProviderFactory) GetMaxCloudEventKeepDays() int { return 30 } +func (self *SQcloudProviderFactory) IsSupportClouduser() bool { + return true +} + +func (self *SQcloudProviderFactory) IsSupportCreateCloudgroup() bool { + return true +} + func (self *SQcloudProviderFactory) ValidateChangeBandwidth(instanceId string, bandwidth int64) error { if len(instanceId) == 0 { return fmt.Errorf("Only changes to the binding machine's EIP bandwidth are supported") @@ -164,6 +172,14 @@ func (self *SQcloudProvider) GetAccountId() string { return self.client.GetAccountId() } +func (self *SQcloudProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SQcloudProvider) IsSupportCloudId() bool { + return cloudprovider.IsSupportCloudId(self) +} + func (self *SQcloudProvider) GetIRegions() []cloudprovider.ICloudRegion { return self.client.GetIRegions() } @@ -201,3 +217,31 @@ func (self *SQcloudProvider) GetStorageClasses(regionId string) []string { func (self *SQcloudProvider) GetCapabilities() []string { return self.client.GetCapabilities() } + +func (self *SQcloudProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SQcloudProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SQcloudProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SQcloudProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} + +func (self *SQcloudProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SQcloudProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetISystemCloudpolicies() +} + +func (self *SQcloudProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go index f4ac4641cd..38c936beae 100644 --- a/pkg/multicloud/qcloud/qcloud.go +++ b/pkg/multicloud/qcloud/qcloud.go @@ -50,6 +50,7 @@ const ( QCLOUD_CLB_API_VERSION = "2018-03-17" QCLOUD_BILLING_API_VERSION = "2018-07-09" QCLOUD_AUDIT_API_VERSION = "2019-03-19" + QCLOUD_CAM_API_VERSION = "2019-01-16" ) type QcloudClientConfig struct { @@ -182,6 +183,11 @@ func billingRequest(client *common.Client, apiName string, params map[string]str return _jsonRequest(client, domain, QCLOUD_BILLING_API_VERSION, apiName, params, debug, true) } +func camRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := "cam.tencentcloudapi.com" + return _jsonRequest(client, domain, QCLOUD_CAM_API_VERSION, apiName, params, debug, true) +} + func monitorRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { domain := "monitor.tencentcloudapi.com" @@ -519,6 +525,14 @@ func (client *SQcloudClient) billingRequest(apiName string, params map[string]st return billingRequest(cli, apiName, params, client.debug) } +func (client *SQcloudClient) camRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return camRequest(cli, apiName, params, client.debug) +} + func (client *SQcloudClient) jsonRequest(apiName string, params map[string]string, retry bool) (jsonutils.JSONObject, error) { cli, err := client.getDefaultClient() if err != nil { @@ -667,6 +681,10 @@ func (client *SQcloudClient) GetAccountId() string { return client.ownerName } +func (client *SQcloudClient) GetIamLoginUrl() string { + return fmt.Sprintf("https://cloud.tencent.com/login/subAccount?account=%s", client.ownerName) +} + func (client *SQcloudClient) GetIRegions() []cloudprovider.ICloudRegion { return client.iregions } diff --git a/pkg/multicloud/qcloud/shell/cloudgroup.go b/pkg/multicloud/qcloud/shell/cloudgroup.go new file mode 100644 index 0000000000..e2c07a5829 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/cloudgroup.go @@ -0,0 +1,108 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CloudgroupListOptions struct { + Keyword string + Page int + Rp int + } + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List cloudgroups", func(cli *qcloud.SRegion, args *CloudgroupListOptions) error { + groups, _, err := cli.GetClient().ListGroups(args.Keyword, args.Page, args.Rp) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type CloudgroupIdOptions struct { + ID int + } + + shellutils.R(&CloudgroupIdOptions{}, "cloud-group-delete", "Delete cloudgroup", func(cli *qcloud.SRegion, args *CloudgroupIdOptions) error { + return cli.GetClient().DeleteGroup(args.ID) + }) + + shellutils.R(&CloudgroupIdOptions{}, "cloud-group-show", "Show cloudgroup", func(cli *qcloud.SRegion, args *CloudgroupIdOptions) error { + group, err := cli.GetClient().GetGroup(args.ID) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupCreateOptions struct { + NAME string + Remark string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create cloudgroup", func(cli *qcloud.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Remark) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type CloudgroupUserListOptions struct { + GROUPID int + Page int + Rp int + } + shellutils.R(&CloudgroupUserListOptions{}, "cloud-group-user-list", "List cloudgroup users", func(cli *qcloud.SRegion, args *CloudgroupUserListOptions) error { + users, _, err := cli.GetClient().ListGroupUsers(args.GROUPID, args.Page, args.Rp) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type GroupUserOptions struct { + GROUP int + USER int + } + + shellutils.R(&GroupUserOptions{}, "cloud-group-add-user", "Add user to cloudgroup", func(cli *qcloud.SRegion, args *GroupUserOptions) error { + return cli.GetClient().AddUserToGroup(args.GROUP, args.USER) + }) + + shellutils.R(&GroupUserOptions{}, "cloud-group-remove-user", "Remove user from cloudgroup", func(cli *qcloud.SRegion, args *GroupUserOptions) error { + return cli.GetClient().RemoveUserFromGroup(args.GROUP, args.USER) + }) + + type GroupPolicyOptions struct { + GROUP int + POLICY string + } + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-attach-policy", "Attach policy to cloudgroup", func(cli *qcloud.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().AttachGroupPolicy(args.GROUP, args.POLICY) + }) + + shellutils.R(&GroupPolicyOptions{}, "cloud-group-detach-policy", "Detach policy from cloudgroup", func(cli *qcloud.SRegion, args *GroupPolicyOptions) error { + return cli.GetClient().DetachGroupPolicy(args.GROUP, args.POLICY) + }) + +} diff --git a/pkg/multicloud/qcloud/shell/clouduser.go b/pkg/multicloud/qcloud/shell/clouduser.go new file mode 100644 index 0000000000..953e99fed2 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/clouduser.go @@ -0,0 +1,138 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserListOptions struct { + } + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List cloudusers", func(cli *qcloud.SRegion, args *ClouduserListOptions) error { + users, err := cli.GetClient().ListUsers() + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type ClouduserOptions struct { + USER string + } + shellutils.R(&ClouduserOptions{}, "cloud-user-delete", "Delete clouduser", func(cli *qcloud.SRegion, args *ClouduserOptions) error { + return cli.GetClient().DeleteUser(args.USER) + }) + + shellutils.R(&ClouduserOptions{}, "cloud-user-show", "Show clouduser", func(cli *qcloud.SRegion, args *ClouduserOptions) error { + user, err := cli.GetClient().GetClouduser(args.USER) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type ClouduserCreateOptions struct { + NAME string + Password string + Desc string + ConsoleLogin bool + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create clouduser", func(cli *qcloud.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateClouduser(args.NAME, args.Password, args.Desc, args.ConsoleLogin) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type ClouduserPolicyOptions struct { + UIN string + POLICY_ID string + } + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-attach-policy", "Attach policy for clouduser", func(cli *qcloud.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().AttachPolicy(args.UIN, args.POLICY_ID) + }) + + shellutils.R(&ClouduserPolicyOptions{}, "cloud-user-detach-policy", "Detach policy from clouduser", func(cli *qcloud.SRegion, args *ClouduserPolicyOptions) error { + return cli.GetClient().DetachPolicy(args.UIN, args.POLICY_ID) + }) + + type ClouduserPolicyListOptions struct { + UIN string + Page int + Rp int + } + + shellutils.R(&ClouduserPolicyListOptions{}, "cloud-user-policy-list", "List policy from clouduser", func(cli *qcloud.SRegion, args *ClouduserPolicyListOptions) error { + policies, _, err := cli.GetClient().ListAttachedUserPolicies(args.UIN, args.Page, args.Rp) + if err != nil { + return err + } + printList(policies, 0, 0, 0, nil) + return nil + }) + + type CloudpolicyListOption struct { + Keyword string + Scope string `choices:"QCS|Local"` + Page int + Rp int + } + + shellutils.R(&CloudpolicyListOption{}, "cloud-policy-list", "List cloudpolicy", func(cli *qcloud.SRegion, args *CloudpolicyListOption) error { + policies, _, err := cli.GetClient().ListPolicies(args.Keyword, args.Scope, args.Page, args.Rp) + if err != nil { + return err + } + printList(policies, 0, 0, 0, nil) + return nil + }) + + type CloudpolicyShowOption struct { + POLICY_ID string + } + + shellutils.R(&CloudpolicyShowOption{}, "cloud-policy-show", "Show cloudpolicy", func(cli *qcloud.SRegion, args *CloudpolicyShowOption) error { + policy, err := cli.GetClient().GetPolicy(args.POLICY_ID) + if err != nil { + return err + } + printObject(policy) + return nil + }) + + type ClouduserGroupListOptions struct { + UIN string + Page int + Rp int + } + + shellutils.R(&ClouduserGroupListOptions{}, "cloud-user-group-list", "List clouduser groups", func(cli *qcloud.SRegion, args *ClouduserGroupListOptions) error { + groups, _, err := cli.GetClient().ListGroupsForUser(args.UIN, args.Rp, args.Page) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/notify/handlers.go b/pkg/notify/handlers.go index 785c2f6306..841c2bf121 100644 --- a/pkg/notify/handlers.go +++ b/pkg/notify/handlers.go @@ -43,7 +43,8 @@ var API_VERSION = "api/v1" func InitHandlers(app *appsrv.Application) { // add version handler with API_VERSION prefix app.AddDefaultHandler("GET", API_VERSION+"/version", appsrv.VersionHandler, "version") - db.AddProjectResourceCountHandler(API_VERSION, app) + db.AddScopeResourceCountHandler(API_VERSION, app) + db.RegisterModelManager(models.ContactManager) db.RegisterModelManager(models.VerifyManager) db.RegisterModelManager(models.NotificationManager) diff --git a/pkg/util/logclient/consts.go b/pkg/util/logclient/consts.go index 94ef914fcf..934e4509f6 100644 --- a/pkg/util/logclient/consts.go +++ b/pkg/util/logclient/consts.go @@ -193,4 +193,10 @@ const ( ACT_DELETE_SCALING_POLICY = "删除伸缩策略" ACT_SAVE_TO_TEMPLATE = "保存为模版" + + ACT_SYNC_POLICIES = "同步权限" + ACT_ADD_USER = "添加用户" + ACT_REMOVE_USER = "移除用户" + ACT_ATTACH_POLICY = "绑定权限" + ACT_DETACH_POLICY = "移除权限" ) diff --git a/scripts/codegen.py b/scripts/codegen.py index db93bfcd97..091a7c8877 100755 --- a/scripts/codegen.py +++ b/scripts/codegen.py @@ -130,6 +130,9 @@ class ModelAPI(FuncDispatcher): self.run(pkg=["keystone", "models"], out=["identity"]) self.run_model("compute") self.run_model("image") + self.run_model("cloudid") + + def gen_monitor(self): self.run(pkg=["monitor", "models"], out=["monitor"]) class SwaggerCode(FuncDispatcher): @@ -165,6 +168,9 @@ class SwaggerCode(FuncDispatcher): def gen_image(self): self.run("image", pkg=["models"], out="image") + def gen_cloudid(self): + self.run("cloudid", pkg=["models"], out="cloudid") + def gen_monitor(self): self.run("monitor", pkg=["models"], out="monitor") @@ -192,6 +198,9 @@ class SwaggerYAML(FuncDispatcher): def gen_image(self): self.run("image") + def gen_cloudid(self): + self.run("cloudid") + def gen_monitor(self): self.run("monitor") diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/vendor/golang.org/x/mod/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/mod/PATENTS b/vendor/golang.org/x/mod/PATENTS new file mode 100644 index 0000000000..733099041f --- /dev/null +++ b/vendor/golang.org/x/mod/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/tools/internal/module/module.go b/vendor/golang.org/x/mod/module/module.go similarity index 57% rename from vendor/golang.org/x/tools/internal/module/module.go rename to vendor/golang.org/x/mod/module/module.go index 9a4edb9dec..6cd37280a8 100644 --- a/vendor/golang.org/x/tools/internal/module/module.go +++ b/vendor/golang.org/x/mod/module/module.go @@ -2,8 +2,86 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package module defines the module.Version type -// along with support code. +// Package module defines the module.Version type along with support code. +// +// The module.Version type is a simple Path, Version pair: +// +// type Version struct { +// Path string +// Version string +// } +// +// There are no restrictions imposed directly by use of this structure, +// but additional checking functions, most notably Check, verify that +// a particular path, version pair is valid. +// +// Escaped Paths +// +// Module paths appear as substrings of file system paths +// (in the download cache) and of web server URLs in the proxy protocol. +// In general we cannot rely on file systems to be case-sensitive, +// nor can we rely on web servers, since they read from file systems. +// That is, we cannot rely on the file system to keep rsc.io/QUOTE +// and rsc.io/quote separate. Windows and macOS don't. +// Instead, we must never require two different casings of a file path. +// Because we want the download cache to match the proxy protocol, +// and because we want the proxy protocol to be possible to serve +// from a tree of static files (which might be stored on a case-insensitive +// file system), the proxy protocol must never require two different casings +// of a URL path either. +// +// One possibility would be to make the escaped form be the lowercase +// hexadecimal encoding of the actual path bytes. This would avoid ever +// needing different casings of a file path, but it would be fairly illegible +// to most programmers when those paths appeared in the file system +// (including in file paths in compiler errors and stack traces) +// in web server logs, and so on. Instead, we want a safe escaped form that +// leaves most paths unaltered. +// +// The safe escaped form is to replace every uppercase letter +// with an exclamation mark followed by the letter's lowercase equivalent. +// +// For example, +// +// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. +// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy +// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. +// +// Import paths that avoid upper-case letters are left unchanged. +// Note that because import paths are ASCII-only and avoid various +// problematic punctuation (like : < and >), the escaped form is also ASCII-only +// and avoids the same problematic punctuation. +// +// Import paths have never allowed exclamation marks, so there is no +// need to define how to escape a literal !. +// +// Unicode Restrictions +// +// Today, paths are disallowed from using Unicode. +// +// Although paths are currently disallowed from using Unicode, +// we would like at some point to allow Unicode letters as well, to assume that +// file systems and URLs are Unicode-safe (storing UTF-8), and apply +// the !-for-uppercase convention for escaping them in the file system. +// But there are at least two subtle considerations. +// +// First, note that not all case-fold equivalent distinct runes +// form an upper/lower pair. +// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) +// are three distinct runes that case-fold to each other. +// When we do add Unicode letters, we must not assume that upper/lower +// are the only case-equivalent pairs. +// Perhaps the Kelvin symbol would be disallowed entirely, for example. +// Or perhaps it would escape as "!!k", or perhaps as "(212A)". +// +// Second, it would be nice to allow Unicode marks as well as letters, +// but marks include combining marks, and then we must deal not +// only with case folding but also normalization: both U+00E9 ('é') +// and U+0065 U+0301 ('e' followed by combining acute accent) +// look the same on the page and are treated by some file systems +// as the same path. If we do allow Unicode marks in paths, there +// must be some kind of normalization to allow only one canonical +// encoding of any character used in an import path. package module // IMPORTANT NOTE @@ -24,22 +102,95 @@ import ( "unicode" "unicode/utf8" - "golang.org/x/tools/internal/semver" + "golang.org/x/mod/semver" + errors "golang.org/x/xerrors" ) -// A Version is defined by a module path and version pair. +// A Version (for clients, a module.Version) is defined by a module path and version pair. +// These are stored in their plain (unescaped) form. type Version struct { + // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2". Path string // Version is usually a semantic version in canonical form. - // There are two exceptions to this general rule. + // There are three exceptions to this general rule. // First, the top-level target of a build has no specific version // and uses Version = "". // Second, during MVS calculations the version "none" is used // to represent the decision to take no version of a given module. + // Third, filesystem paths found in "replace" directives are + // represented by a path with an empty version. Version string `json:",omitempty"` } +// String returns a representation of the Version suitable for logging +// (Path@Version, or just Path if Version is empty). +func (m Version) String() string { + if m.Version == "" { + return m.Path + } + return m.Path + "@" + m.Version +} + +// A ModuleError indicates an error specific to a module. +type ModuleError struct { + Path string + Version string + Err error +} + +// VersionError returns a ModuleError derived from a Version and error, +// or err itself if it is already such an error. +func VersionError(v Version, err error) error { + var mErr *ModuleError + if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version { + return err + } + return &ModuleError{ + Path: v.Path, + Version: v.Version, + Err: err, + } +} + +func (e *ModuleError) Error() string { + if v, ok := e.Err.(*InvalidVersionError); ok { + return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err) + } + if e.Version != "" { + return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err) + } + return fmt.Sprintf("module %s: %v", e.Path, e.Err) +} + +func (e *ModuleError) Unwrap() error { return e.Err } + +// An InvalidVersionError indicates an error specific to a version, with the +// module path unknown or specified externally. +// +// A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError +// must not wrap a ModuleError. +type InvalidVersionError struct { + Version string + Pseudo bool + Err error +} + +// noun returns either "version" or "pseudo-version", depending on whether +// e.Version is a pseudo-version. +func (e *InvalidVersionError) noun() string { + if e.Pseudo { + return "pseudo-version" + } + return "version" +} + +func (e *InvalidVersionError) Error() string { + return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err) +} + +func (e *InvalidVersionError) Unwrap() error { return e.Err } + // Check checks that a given module path, version pair is valid. // In addition to the path being a valid module path // and the version being a valid semantic version, @@ -51,17 +202,14 @@ func Check(path, version string) error { return err } if !semver.IsValid(version) { - return fmt.Errorf("malformed semantic version %v", version) + return &ModuleError{ + Path: path, + Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")}, + } } _, pathMajor, _ := SplitPathVersion(path) - if !MatchPathMajor(version, pathMajor) { - if pathMajor == "" { - pathMajor = "v0 or v1" - } - if pathMajor[0] == '.' { // .v1 - pathMajor = pathMajor[1:] - } - return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor) + if err := CheckPathMajor(version, pathMajor); err != nil { + return &ModuleError{Path: path, Err: err} } return nil } @@ -79,7 +227,7 @@ func firstPathOK(r rune) bool { // Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~. // This matches what "go get" has historically recognized in import paths. // TODO(rsc): We would like to allow Unicode letters, but that requires additional -// care in the safe encoding (see note below). +// care in the safe encoding (see "escaped paths" above). func pathOK(r rune) bool { if r < utf8.RuneSelf { return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' || @@ -94,7 +242,7 @@ func pathOK(r rune) bool { // For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters. // If we expand the set of allowed characters here, we have to // work harder at detecting potential case-folding and normalization collisions. -// See note about "safe encoding" below. +// See note about "escaped paths" above. func fileNameOK(r rune) bool { if r < utf8.RuneSelf { // Entire set of ASCII punctuation, from which we remove characters: @@ -120,6 +268,17 @@ func fileNameOK(r rune) bool { } // CheckPath checks that a module path is valid. +// A valid module path is a valid import path, as checked by CheckImportPath, +// with two additional constraints. +// First, the leading path element (up to the first slash, if any), +// by convention a domain name, must contain only lower-case ASCII letters, +// ASCII digits, dots (U+002E), and dashes (U+002D); +// it must contain at least one dot and cannot start with a dash. +// Second, for a final path element of the form /vN, where N looks numeric +// (ASCII digits and dots) must not begin with a leading zero, must not be /v1, +// and must not contain any dots. For paths beginning with "gopkg.in/", +// this second requirement is replaced by a requirement that the path +// follow the gopkg.in server's conventions. func CheckPath(path string) error { if err := checkPath(path, false); err != nil { return fmt.Errorf("malformed module path %q: %v", path, err) @@ -149,6 +308,20 @@ func CheckPath(path string) error { } // CheckImportPath checks that an import path is valid. +// +// A valid import path consists of one or more valid path elements +// separated by slashes (U+002F). (It must not begin with nor end in a slash.) +// +// A valid path element is a non-empty string made up of +// ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~. +// It must not begin or end with a dot (U+002E), nor contain two dots in a row. +// +// The element prefix up to the first dot must not be a reserved file name +// on Windows, regardless of case (CON, com1, NuL, and so on). +// +// CheckImportPath may be less restrictive in the future, but see the +// top-level package documentation for additional information about +// subtleties of Unicode. func CheckImportPath(path string) error { if err := checkPath(path, false); err != nil { return fmt.Errorf("malformed import path %q: %v", path, err) @@ -169,8 +342,8 @@ func checkPath(path string, fileName bool) error { if path == "" { return fmt.Errorf("empty string") } - if strings.Contains(path, "..") { - return fmt.Errorf("double dot") + if path[0] == '-' { + return fmt.Errorf("leading dash") } if strings.Contains(path, "//") { return fmt.Errorf("double slash") @@ -226,13 +399,24 @@ func checkElem(elem string, fileName bool) error { } for _, bad := range badWindowsNames { if strings.EqualFold(bad, short) { - return fmt.Errorf("disallowed path element %q", elem) + return fmt.Errorf("%q disallowed as path element component on Windows", short) } } return nil } -// CheckFilePath checks whether a slash-separated file path is valid. +// CheckFilePath checks that a slash-separated file path is valid. +// The definition of a valid file path is the same as the definition +// of a valid import path except that the set of allowed characters is larger: +// all Unicode letters, ASCII digits, the ASCII space character (U+0020), +// and the ASCII punctuation characters +// “!#$%&()+,-.=@[]^_{}~”. +// (The excluded punctuation characters, " * < > ? ` ' | / \ and :, +// have special meanings in certain shells or operating systems.) +// +// CheckFilePath may be less restrictive in the future, but see the +// top-level package documentation for additional information about +// subtleties of Unicode. func CheckFilePath(path string) error { if err := checkPath(path, true); err != nil { return fmt.Errorf("malformed file path %q: %v", path, err) @@ -271,6 +455,9 @@ var badWindowsNames = []string{ // and version is either empty or "/vN" for N >= 2. // As a special case, gopkg.in paths are recognized directly; // they require ".vN" instead of "/vN", and for all N, not just N >= 2. +// SplitPathVersion returns with ok = false when presented with +// a path whose last path element does not satisfy the constraints +// applied by CheckPath, such as "example.com/pkg/v1" or "example.com/pkg/v1.2". func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { if strings.HasPrefix(path, "gopkg.in/") { return splitGopkgIn(path) @@ -319,20 +506,65 @@ func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { // MatchPathMajor reports whether the semantic version v // matches the path major version pathMajor. +// +// MatchPathMajor returns true if and only if CheckPathMajor returns nil. func MatchPathMajor(v, pathMajor string) bool { + return CheckPathMajor(v, pathMajor) == nil +} + +// CheckPathMajor returns a non-nil error if the semantic version v +// does not match the path major version pathMajor. +func CheckPathMajor(v, pathMajor string) error { + // TODO(jayconrod): return errors or panic for invalid inputs. This function + // (and others) was covered by integration tests for cmd/go, and surrounding + // code protected against invalid inputs like non-canonical versions. if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { pathMajor = strings.TrimSuffix(pathMajor, "-unstable") } if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" { // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1. // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405. - return true + return nil } m := semver.Major(v) if pathMajor == "" { - return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" + if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" { + return nil + } + pathMajor = "v0 or v1" + } else if pathMajor[0] == '/' || pathMajor[0] == '.' { + if m == pathMajor[1:] { + return nil + } + pathMajor = pathMajor[1:] } - return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:] + return &InvalidVersionError{ + Version: v, + Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)), + } +} + +// PathMajorPrefix returns the major-version tag prefix implied by pathMajor. +// An empty PathMajorPrefix allows either v0 or v1. +// +// Note that MatchPathMajor may accept some versions that do not actually begin +// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' +// pathMajor, even though that pathMajor implies 'v1' tagging. +func PathMajorPrefix(pathMajor string) string { + if pathMajor == "" { + return "" + } + if pathMajor[0] != '/' && pathMajor[0] != '.' { + panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator") + } + if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { + pathMajor = strings.TrimSuffix(pathMajor, "-unstable") + } + m := pathMajor[1:] + if m != semver.Major(m) { + panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version") + } + return m } // CanonicalVersion returns the canonical form of the version string v. @@ -345,7 +577,10 @@ func CanonicalVersion(v string) string { return cv } -// Sort sorts the list by Path, breaking ties by comparing Versions. +// Sort sorts the list by Path, breaking ties by comparing Version fields. +// The Version fields are interpreted as semantic versions (using semver.Compare) +// optionally followed by a tie-breaking suffix introduced by a slash character, +// like in "v0.0.1/go.mod". func Sort(list []Version) { sort.Slice(list, func(i, j int) bool { mi := list[i] @@ -372,93 +607,36 @@ func Sort(list []Version) { }) } -// Safe encodings -// -// Module paths appear as substrings of file system paths -// (in the download cache) and of web server URLs in the proxy protocol. -// In general we cannot rely on file systems to be case-sensitive, -// nor can we rely on web servers, since they read from file systems. -// That is, we cannot rely on the file system to keep rsc.io/QUOTE -// and rsc.io/quote separate. Windows and macOS don't. -// Instead, we must never require two different casings of a file path. -// Because we want the download cache to match the proxy protocol, -// and because we want the proxy protocol to be possible to serve -// from a tree of static files (which might be stored on a case-insensitive -// file system), the proxy protocol must never require two different casings -// of a URL path either. -// -// One possibility would be to make the safe encoding be the lowercase -// hexadecimal encoding of the actual path bytes. This would avoid ever -// needing different casings of a file path, but it would be fairly illegible -// to most programmers when those paths appeared in the file system -// (including in file paths in compiler errors and stack traces) -// in web server logs, and so on. Instead, we want a safe encoding that -// leaves most paths unaltered. -// -// The safe encoding is this: -// replace every uppercase letter with an exclamation mark -// followed by the letter's lowercase equivalent. -// -// For example, -// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. -// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy -// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. -// -// Import paths that avoid upper-case letters are left unchanged. -// Note that because import paths are ASCII-only and avoid various -// problematic punctuation (like : < and >), the safe encoding is also ASCII-only -// and avoids the same problematic punctuation. -// -// Import paths have never allowed exclamation marks, so there is no -// need to define how to encode a literal !. -// -// Although paths are disallowed from using Unicode (see pathOK above), -// the eventual plan is to allow Unicode letters as well, to assume that -// file systems and URLs are Unicode-safe (storing UTF-8), and apply -// the !-for-uppercase convention. Note however that not all runes that -// are different but case-fold equivalent are an upper/lower pair. -// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) -// are considered to case-fold to each other. When we do add Unicode -// letters, we must not assume that upper/lower are the only case-equivalent pairs. -// Perhaps the Kelvin symbol would be disallowed entirely, for example. -// Or perhaps it would encode as "!!k", or perhaps as "(212A)". -// -// Also, it would be nice to allow Unicode marks as well as letters, -// but marks include combining marks, and then we must deal not -// only with case folding but also normalization: both U+00E9 ('é') -// and U+0065 U+0301 ('e' followed by combining acute accent) -// look the same on the page and are treated by some file systems -// as the same path. If we do allow Unicode marks in paths, there -// must be some kind of normalization to allow only one canonical -// encoding of any character used in an import path. - -// EncodePath returns the safe encoding of the given module path. +// EscapePath returns the escaped form of the given module path. // It fails if the module path is invalid. -func EncodePath(path string) (encoding string, err error) { +func EscapePath(path string) (escaped string, err error) { if err := CheckPath(path); err != nil { return "", err } - return encodeString(path) + return escapeString(path) } -// EncodeVersion returns the safe encoding of the given module version. +// EscapeVersion returns the escaped form of the given module version. // Versions are allowed to be in non-semver form but must be valid file names // and not contain exclamation marks. -func EncodeVersion(v string) (encoding string, err error) { +func EscapeVersion(v string) (escaped string, err error) { if err := checkElem(v, true); err != nil || strings.Contains(v, "!") { - return "", fmt.Errorf("disallowed version string %q", v) + return "", &InvalidVersionError{ + Version: v, + Err: fmt.Errorf("disallowed version string"), + } } - return encodeString(v) + return escapeString(v) } -func encodeString(s string) (encoding string, err error) { +func escapeString(s string) (escaped string, err error) { haveUpper := false for _, r := range s { if r == '!' || r >= utf8.RuneSelf { // This should be disallowed by CheckPath, but diagnose anyway. - // The correctness of the encoding loop below depends on it. - return "", fmt.Errorf("internal error: inconsistency in EncodePath") + // The correctness of the escaping loop below depends on it. + return "", fmt.Errorf("internal error: inconsistency in EscapePath") } if 'A' <= r && r <= 'Z' { haveUpper = true @@ -480,39 +658,39 @@ func encodeString(s string) (encoding string, err error) { return string(buf), nil } -// DecodePath returns the module path of the given safe encoding. -// It fails if the encoding is invalid or encodes an invalid path. -func DecodePath(encoding string) (path string, err error) { - path, ok := decodeString(encoding) +// UnescapePath returns the module path for the given escaped path. +// It fails if the escaped path is invalid or describes an invalid path. +func UnescapePath(escaped string) (path string, err error) { + path, ok := unescapeString(escaped) if !ok { - return "", fmt.Errorf("invalid module path encoding %q", encoding) + return "", fmt.Errorf("invalid escaped module path %q", escaped) } if err := CheckPath(path); err != nil { - return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err) + return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err) } return path, nil } -// DecodeVersion returns the version string for the given safe encoding. -// It fails if the encoding is invalid or encodes an invalid version. +// UnescapeVersion returns the version string for the given escaped version. +// It fails if the escaped form is invalid or describes an invalid version. // Versions are allowed to be in non-semver form but must be valid file names // and not contain exclamation marks. -func DecodeVersion(encoding string) (v string, err error) { - v, ok := decodeString(encoding) +func UnescapeVersion(escaped string) (v string, err error) { + v, ok := unescapeString(escaped) if !ok { - return "", fmt.Errorf("invalid version encoding %q", encoding) + return "", fmt.Errorf("invalid escaped version %q", escaped) } if err := checkElem(v, true); err != nil { - return "", fmt.Errorf("disallowed version string %q", v) + return "", fmt.Errorf("invalid escaped version %q: %v", v, err) } return v, nil } -func decodeString(encoding string) (string, bool) { +func unescapeString(escaped string) (string, bool) { var buf []byte bang := false - for _, r := range encoding { + for _, r := range escaped { if r >= utf8.RuneSelf { return "", false } diff --git a/vendor/golang.org/x/tools/internal/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go similarity index 99% rename from vendor/golang.org/x/tools/internal/semver/semver.go rename to vendor/golang.org/x/mod/semver/semver.go index 4af7118e55..2988e3cf9c 100644 --- a/vendor/golang.org/x/tools/internal/semver/semver.go +++ b/vendor/golang.org/x/mod/semver/semver.go @@ -107,7 +107,7 @@ func Build(v string) string { } // Compare returns an integer comparing two versions according to -// according to semantic version precedence. +// semantic version precedence. // The result will be 0 if v == w, -1 if v < w, or +1 if v > w. // // An invalid semantic version string is considered less than a valid one. @@ -263,7 +263,7 @@ func parseBuild(v string) (t, rest string, ok bool) { i := 1 start := 1 for i < len(v) { - if !isIdentChar(v[i]) { + if !isIdentChar(v[i]) && v[i] != '.' { return } if v[i] == '.' { diff --git a/vendor/golang.org/x/net/http2/http2.go b/vendor/golang.org/x/net/http2/http2.go index bdaba1d46b..27cc893cc0 100644 --- a/vendor/golang.org/x/net/http2/http2.go +++ b/vendor/golang.org/x/net/http2/http2.go @@ -19,7 +19,6 @@ package http2 // import "golang.org/x/net/http2" import ( "bufio" "crypto/tls" - "errors" "fmt" "io" "net/http" @@ -173,11 +172,6 @@ func (s SettingID) String() string { return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s)) } -var ( - errInvalidHeaderFieldName = errors.New("http2: invalid header field name") - errInvalidHeaderFieldValue = errors.New("http2: invalid header field value") -) - // validWireHeaderFieldName reports whether v is a valid header field // name (key). See httpguts.ValidHeaderName for the base rules. // diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index de31d72b2c..bc9e41a1b7 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -581,13 +581,10 @@ type stream struct { cancelCtx func() // owned by serverConn's serve loop: - bodyBytes int64 // body bytes seen so far - declBodyBytes int64 // or -1 if undeclared - flow flow // limits writing from Handler to client - inflow flow // what the client is allowed to POST/etc to us - parent *stream // or nil - numTrailerValues int64 - weight uint8 + bodyBytes int64 // body bytes seen so far + declBodyBytes int64 // or -1 if undeclared + flow flow // limits writing from Handler to client + inflow flow // what the client is allowed to POST/etc to us state streamState resetQueued bool // RST_STREAM queued for write; set by sc.resetStream gotTrailerHeader bool // HEADER frame for trailers was seen diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 8e0059db66..81778bec61 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -93,7 +93,7 @@ type Transport struct { // send in the initial settings frame. It is how many bytes // of response headers are allowed. Unlike the http2 spec, zero here // means to use a default limit (currently 10MB). If you actually - // want to advertise an ulimited value to the peer, Transport + // want to advertise an unlimited value to the peer, Transport // interprets the highest possible value here (0xffffffff or 1<<32-1) // to mean no limit. MaxHeaderListSize uint32 @@ -2198,8 +2198,6 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error { return nil } -var errInvalidTrailers = errors.New("http2: invalid trailers") - func (rl *clientConnReadLoop) endStream(cs *clientStream) { // TODO: check that any declared content-length matches, like // server.go's (*stream).endStream method. @@ -2430,7 +2428,6 @@ func (cc *ClientConn) writeStreamReset(streamID uint32, code ErrCode, err error) var ( errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit") errRequestHeaderListSize = errors.New("http2: request header list larger than peer's advertised limit") - errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers") ) func (cc *ClientConn) logf(format string, args ...interface{}) { diff --git a/vendor/golang.org/x/net/ipv4/helper.go b/vendor/golang.org/x/net/ipv4/helper.go index b494a2cde4..e845a7376e 100644 --- a/vendor/golang.org/x/net/ipv4/helper.go +++ b/vendor/golang.org/x/net/ipv4/helper.go @@ -13,16 +13,13 @@ import ( ) var ( - errInvalidConn = errors.New("invalid connection") - errMissingAddress = errors.New("missing address") - errMissingHeader = errors.New("missing header") - errNilHeader = errors.New("nil header") - errHeaderTooShort = errors.New("header too short") - errExtHeaderTooShort = errors.New("extension header too short") - errInvalidConnType = errors.New("invalid conn type") - errNoSuchInterface = errors.New("no such interface") - errNoSuchMulticastInterface = errors.New("no such multicast interface") - errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH) + errInvalidConn = errors.New("invalid connection") + errMissingAddress = errors.New("missing address") + errNilHeader = errors.New("nil header") + errHeaderTooShort = errors.New("header too short") + errExtHeaderTooShort = errors.New("extension header too short") + errInvalidConnType = errors.New("invalid conn type") + errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH) // See https://www.freebsd.org/doc/en/books/porters-handbook/versions.html. freebsdVersion uint32 diff --git a/vendor/golang.org/x/net/ipv4/sys_asmreq.go b/vendor/golang.org/x/net/ipv4/sys_asmreq.go index c5eaafe96b..76d670acaa 100644 --- a/vendor/golang.org/x/net/ipv4/sys_asmreq.go +++ b/vendor/golang.org/x/net/ipv4/sys_asmreq.go @@ -7,12 +7,15 @@ package ipv4 import ( + "errors" "net" "unsafe" "golang.org/x/net/internal/socket" ) +var errNoSuchInterface = errors.New("no such interface") + func (so *sockOpt) setIPMreq(c *socket.Conn, ifi *net.Interface, grp net.IP) error { mreq := ipMreq{Multiaddr: [4]byte{grp[0], grp[1], grp[2], grp[3]}} if err := setIPMreqInterface(&mreq, ifi); err != nil { diff --git a/vendor/golang.org/x/net/ipv6/helper.go b/vendor/golang.org/x/net/ipv6/helper.go index f767b1f5dd..c2d508f9c3 100644 --- a/vendor/golang.org/x/net/ipv6/helper.go +++ b/vendor/golang.org/x/net/ipv6/helper.go @@ -15,7 +15,6 @@ var ( errMissingAddress = errors.New("missing address") errHeaderTooShort = errors.New("header too short") errInvalidConnType = errors.New("invalid conn type") - errNoSuchInterface = errors.New("no such interface") errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH) ) diff --git a/vendor/golang.org/x/net/publicsuffix/table.go b/vendor/golang.org/x/net/publicsuffix/table.go index 8eb0495edd..369e44656a 100644 --- a/vendor/golang.org/x/net/publicsuffix/table.go +++ b/vendor/golang.org/x/net/publicsuffix/table.go @@ -2,7 +2,7 @@ package publicsuffix -const version = "publicsuffix.org's public_suffix_list.dat, git revision 41cb01612341c7ce3bcdd0cc4e696ae9f6416600 (2019-11-08T08:48:39Z)" +const version = "publicsuffix.org's public_suffix_list.dat, git revision 7922d7c20e246552be418e8f72e577899fd30d99 (2020-02-18T23:18:19Z)" const ( nodesBitsChildren = 10 @@ -23,487 +23,488 @@ const ( ) // numTLD is the number of top level domains. -const numTLD = 1538 +const numTLD = 1528 // Text is the combined text of all labels. const text = "9guacuiababia-goracleaningroks-theatree12hpalermomahachijoinvill" + "eksvik12ix4432-balsfjordd-dnsiskinkyotobetsulikes-piedmonticello" + - "dingenaturhistorisches3-ap-south-16-b-dataikikonaikawachinaganoh" + - "aramcoachampionshiphoptobishimadridvagsoyereportatarantours3-ap-" + - "northeast-2038bloombergbauerninomiyakonojorpelandiyusuharabloxcm" + - "s3-website-us-west-2bluedancebmoattachments5yusuisservehumourbms" + - "akyotanabellunord-aurdalvdalaskanittedallasalleangaviikaascolipi" + - "cenodumetlifeinsurancebmwedeployuu2-localhostoregontrailroadnpar" + - "isor-fronirasakindigenaklodzkochikushinonsenergyuzawabnrwegrowei" + - "bolognagasakimobetsuitainaioirasebastopologyeongnamegawakayamaga" + - "zineat-urlimitedrangedalimoliseoullensvanguardray-dnsupdaternopi" + - "lawatchesalangenishiazaindustriabomloabathsbcatholicaxiashorokan" + - "aiebondrayddnsfreebox-osascoli-picenordre-landraydnsalondonetska" + - "rlsoybonnishigobookinghostfoldnavyboomlahppiacenzachpomorskienis" + - "hiharaboschaefflerdalinkyard-cloudnsaltdalivornobostikarmoybosto" + - "nakijinsekikogentingloboavistanbulsan-sudtirolombardynaliaskimit" + - "subatamibugattiffanynysadoes-itvedestrandrivefsnillfjordrobaknol" + - "uoktachikawakembuchikumagayagawakkanaibetsubamericanfamilydsclou" + - "deitychyattorneyagawafflecellclaimsaludrudupontariodejaneirodoyb" + - "otanicalgardenishiizunazukindustriesteamfamberkeleybotanicgarden" + - "ishikatakazakinfinitintuitjxfinitybotanybouncemerckmsdnipropetro" + - "vskjervoyagebounty-fullensakerrypropertiesalvadordalibabalestran" + - "dabergamo-siemensncfdurbanamexnethnologyboutiquebecheltenham-rad" + - "io-openairbusantiquest-a-la-maisondre-landroidurhamburglogoweirb" + - "ozen-sudtirolomzaporizhzhegurinuyamashinatsukigatakasakitchenish" + - "ikatsuragit-reposalzburgloppenzaolbia-tempio-olbiatempioolbialys" + - "tokkepnogatagajobojinvestmentsamegawabozen-suedtirolondrinamssko" + - "ganeinzais-a-candidatebplacedogawarabikomaezakirunorddalorenskog" + - "lugmbhartipscbgminakamichiharabrandywinevalleybrasiliabrindisibe" + - "nikinderoybristoloseyouriparliamentkmaxxjavald-aostarostwodzisla" + - "wellbeingzonebritishcolumbialowiezaganquanpachigasakievennodesab" + - "aerobaticketsamnangerbroadcastleclerchernihivgubsampalacebroadwa" + - "ybroke-itksatxn--0trq7p7nnishikawazukamisunagawabrokerbronnoysun" + - "dweberbrothermesaverdealstahaugesunderseaportsinfolldalotenkawab" + - "rowsersafetymarketsamsclubartowfarmsteadynathomebuiltmparmattele" + - "fonicarbonia-iglesias-carboniaiglesiascarboniabrumunddalottebrun" + - "elasticbeanstalkarpaczeladzparochernigovernmentoyosatoyokawabrus" + - "selsamsungmodellingmxn--11b4c3dyndns-at-homedepotenzamamidsundyn" + - "dns-at-workisboringrimstadyndns-blogdnsandnessjoenishimerabruxel" + - "lesandoybryansklepparsandvikcoromantovalle-d-aostaticsanfrancisc" + - "ofreakunemurorangeiseiyoichippubetsubetsugarugbydgoszczecinemage" + - "ntositecnologiabrynewhollandyndns-freeboxosloftranakanojoetsuwan" + - "ouchikujogaszkolajollamericanexpressexybuskerudinewmexicoalottok" + - "onamegatakatsukis-a-catererbuzentsujiiebuzzwhalingripebwhoswhokk" + - "sundyndns-homednsangobzhitomirumalatvuopmicrolightingriwataraidy" + - "ndns-ipartis-a-celticsfanishinomiyashironobzzcolognexus-2colonia" + - "lwilliamsburgrongausdalukowiiheyakumoldeloittemp-dnsaogoncartier" + - "coloradoplateaudiocolumbusheycommunecommunitycomoarekecomparemar" + - "kerryhotelsaotomeloyalistoragecompute-1computerhistoryofscience-" + - "fictioncomsecuritytacticsapporocondoshichinohealth-carereformina" + - "miechizenconferenceconstructionconsuladonnakaiwamizawassamukawat" + - "aricoharuovatranoyconsultanthropologyconsultingrossetouchihayaak" + - "asakawaharacontactransportecontagematsubaracontemporaryarteducat" + - "ionalchikugodontexistmein-iservebeercontractorskenconventureshin" + - "odebalancertificationcookingchannelsdvrdnsfor-better-thanawatcha" + - "ndclockashiwaracooluroycooperativano-frankivskolegallocus-3copen" + - "hagencyclopedichiryukyuragifuchungbukharanzanishinoomotegocorsic" + - "afederationcorvettemasekashiwazakiyosatokamachintaifun-dnsdojols" + - "tercosenzakopanecosidnshome-webserverdalutskasukabedzin-berlinda" + - "sdaburcostumedicinakamagayahabaghdadyndns-workshopitsitevadsobet" + - "sumidatlantichitachinakagawashtenawdev-myqnapcloudcontrolledekag" + - "aminogiftsanjotoyotapartsannanishinoshimatsuuracouchpotatofriesa" + - "rdegnaroycounciluxurycouponsardiniacq-acranbrookuwanalyticsarluz" + - "erncrdyndns1creditcardynnsarpsborgroundhandlingroznycreditunionc" + - "remonashgabadaddjaguarqhachirogatakanezawacrewildlifedorainfracl" + - "ouderacricketrzyncrimeast-kazakhstanangercrotonecrownipasadenara" + - "shinocrsvpassagensarufutsunomiyawakasaikaitakoelncruisesasayamac" + - "ryptonomichigangwoncuisinellair-traffic-controlleyculturalcenter" + - "tainmentransurlvivanovoldacuneocupcakecuritibahcavuotnagaivuotna" + - "gaokakyotambabyeniwaizumiotsukumiyamazonawsagaeroclubmedecincinn" + - "ationwidealercxn--12c1fe0bradescorporationcymrussiacyonabarumina" + - "mifuranocyoutheworkpccwilliamhillferrerotikagoshimalvikasumigaur" + - "awa-mazowszextraspace-to-rentalstomakomaibarafetsundynuconnectra" + - "paniizafgruefhvalerfidoomdnstracefieldynv6figueresinstaginguideg" + - "reefilateliafilegear-audnedalnfilegear-deatnufcfanfilegear-gbizf" + - "ilegear-iefilegear-jpmorganfilegear-sguitarsavannahgafilminamiiz" + - "ukamiokameokameyamatotakadafinalfinancefineartsaves-the-whalessa" + - "ndria-trani-barletta-andriatranibarlettaandriafinlandynvpnplus-4" + - "finnoyfirebaseapplinzis-a-financialadvisor-aurdalfirenzefireston" + - "efirmdalegoldpoint2thisamitsukefishingolffansavonarusawafitjarvo" + - "dkafjordyroyrvikingrpassenger-associationfitnessettlementraveler" + - "sinsurancefjalerflesbergujohanamakinoharaflickragerogersaxofligh" + - "tschoenbrunnflirfloginlinefloraflorencefloridattorelayfloripader" + - "bornfloristanohatakaharulminamimakis-a-geekasuyanagawaflorokunoh" + - "ealthcareerschokokekschokoladenflowerscholarshipschoolschulefltr" + - "dflynnhosting-clusterflynnhubanzaicloudcontrolappleborkdalpha-my" + - "qnapcloud66fndfor-ourfor-somedizinhistorischeschwarzgwangjuniper" + - "for-theaterforexrothadanorthwesternmutualforgotdnschweizforli-ce" + - "sena-forlicesenaforlikescandyn53forsaleikangerforsandasuologoipa" + - "triafortalfortmissoulancashirecreationfortworthadselectrentin-su" + - "d-tirolforumzfosnesciencecentersciencehistoryfotaris-a-greenfoxf" + - "ordebianfozorafredrikstadtvscientistordalfreeddnsgeekgalaxyfreed" + - "esktopocznore-og-uvdalfreemasonryfreesitexaskoyabearalvahkihokum" + - "akogengerdalcesurancechirealmpmnfreetlscjohnsonfreiburgulenfreig" + - "htrentin-sudtirolfreseniuscountryestateofdelawareggio-calabriafr" + - "ibourgunmaoris-a-gurulvikaszubyfriuli-v-giuliafriuli-ve-giuliafr" + - "iuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgi" + - "uliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giu" + - "liafriuliveneziagiuliafriulivgiuliafrlfroganscotlandfrognfroland" + - "from-akrehamnfrom-alfrom-arfrom-azimuthdfcbankatowicefrom-capebr" + - "etonamicrosoftbankatsushikabeeldengeluidfrom-codyn-vpndnscrapper" + - "-sitefrom-ctrentin-sued-tirolfrom-dchitosetogakushimotoganewspap" + - "erfrom-dedyn-berlincolnfrom-flanderscrappinguovdageaidnunusualpe" + - "rsonfrom-gaulardalfrom-hichisochildrensgardenfrom-iafrom-idfrom-" + - "ilfrom-in-brbarcelonagareyamaizuruhrhcloudiscoveryombolzano-alto" + - "adigeu-3from-kscrysechocolatelemarkaruizawafrom-kyowariasahikawa" + - "winbarclaycards3-fips-us-gov-west-1from-lancasterfrom-mamurogawa" + - "from-mdfrom-meeresistancefrom-mifunefrom-mnfrom-modalenfrom-mser" + - "veirchofunatoriginstitutelevisionishiokoppegardyndns-mailouvreit" + - "oyotomiyazakis-a-chefarsundyndns-office-on-the-webhareidsbergeni" + - "shitosashimizunaminamibosogndalowiczest-le-patronishiwakis-a-con" + - "servativegarsheis-a-cpadualstackhero-networkinggroupartyfrom-mtn" + - "from-nctulanciafrom-ndfrom-nefrom-nh-serveblogsiteleafamilycompa" + - "nyminamiminowafrom-njaworznoticiasnesoddenmarkhangelskjakdneprop" + - "etrovskiervaapsteiermarkatsuyamarylandfrom-nminamioguni5from-nva" + - "lled-aostamayufuettertdasnetzfrom-nyfrom-ohkurafrom-oketogurafro" + - "m-orfrom-padovaksdalfrom-pratohmangonohejis-a-hard-workerservemi" + - "necraftrentin-suedtirolfrom-ris-a-hunterfrom-schmidtre-gauldalfr" + - "om-sdfrom-tnfrom-txn--12co0c3b4evalleaostavernfrom-utazuerichard" + - "lillehammerfeste-ipaviancarrdfrom-val-daostavalleyfrom-vtrentino" + - "-a-adigefrom-wafrom-wielunnerfrom-wvalledaostaobaomoriguchiharag" + - "usartservemp3from-wyfrosinonefrostalowa-wolawafroyahooguyfstcgro" + - "upfizerfujiiderafujikawaguchikonefujiminokamoenairlinedre-eikerf" + - "ujinomiyadavvenjargap-northeast-3fujiokayamangyshlakasamatsudovr" + - "e-eikerfujisatoshonairportland-4-salernoboribetsuckservep2pgfogg" + - "iafujisawafujishiroishidakabiratoridefenseljordfujitsurugashiman" + - "iwakuratextileirfjordfujixeroxn--1ck2e1barclays3-sa-east-1fujiyo" + - "shidavvesiidatsunanjoburgushikamifuranorth-kazakhstanfukayabeats" + - "ervepicservequakefukuchiyamadazaifudaigojomedio-campidano-medioc" + - "ampidanomediofukudominichonanbulsan-suedtirolpusercontentoyotsuk" + - "aidofukuis-a-knightpointtohobby-sitefukumitsubishigakisarazureco" + - "ntainerdpolicefukuokazakishiwadafukuroishikarikaturindalfukusaki" + - "sofukushimannorfolkebibleirvikaufenfukuyamagatakahatakaishimogos" + - "enfunabashiriuchinadafunagatakamatsukawafunahashikamiamakusatsum" + - "asendaisennangooglecodespotaruis-a-landscaperfundaciofuoiskujuku" + - "riyamansionservesarcasmatartanddesignfuosskoczowindmillfurniture" + - "ggio-emilia-romagnakatombetsumitakagiizefurubirafurudonostiaafur" + - "ukawairtelebitballooningwiddleitungsenfusodegaurafussaikisosakit" + - "agawafutabayamaguchinomigawafutboldlygoingnowhere-for-morenakats" + - "ugawafuttsurugimperiafuturecmservicesevastopolefuturehostingxn--" + - "1ctwolominamatargivestbytemarkautokeinotteroyfuturemailingfvgfyl" + - "kesbiblackbaudcdn77-securebungoonord-odalwaysdatabaseballangenka" + - "inanaejrietisalatinabenonichoseiroumuenchenissandiegofyresdalhan" + - "gglidinghangoutsystemscloudyclusterhannanmokuizumodenakayamarbur" + - "ghannosegawahanyuzenhapmirharstadharvestcelebrationhasamarcheapi" + - "geelvinckazoologyhasaminami-alpsewindowsharis-a-libertarianhashb" + - "anghasudahasura-appharmaciensharpharmacyshawaiijimarnardalhasvik" + - "azunow-dnshellaspeziahatogayaizuwakamatsubushikusakadogawahatoya" + - "mazakitakamiizumisanofidelityhatsukaichikaiseis-a-linux-useranis" + - "hiaritabashijonawatehattfjelldalhayashimamotobungotakadapliernew" + - "jerseyhazuminobusells-for-unzenhelsinkitakatakaokalmykiahembygds" + - "forbundhemneshimojis-a-llamarriottrentino-aadigehemsedalhepforge" + - "herokussldheroyhgtvallee-aosteroyhigashiagatsumagoianiahigashich" + - "ichibunkyonanaoshimageandsoundandvisionhigashihiroshimanehigashi" + - "izumozakitakyushuaiahigashikagawahigashikagurasoedahigashikawaki" + - "taaikitamihamadahigashikurumeetnedalhigashimatsushimaritimodernh" + - "igashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshimo" + - "kawahigashinarusells-itrentino-alto-adigehigashinehigashiomihach" + - "imanaustdalhigashiosakasayamanakakogawahigashishirakawamatakaraz" + - "ukaluganskypehigashisumiyoshikawaminamiaikitamotosumy-gatewayhig" + - "ashitsunoshiroomurahigashiurausukitanakagusukumoduminamisanrikub" + - "etsurfastly-terrariuminamiiserniahigashiyamatokoriyamanashiftedi" + - "tchyouriphdhigashiyodogawahigashiyoshinogaris-a-musicianhiraizum" + - "isatohnoshoooshikamaishimodatehirakatashinagawahiranairtrafficpl" + - "exus-1hirarahiratsukagawahirayakagehistorichouseshimokitayamahit" + - "achiomiyagildeskaliszhitachiotagoppdalhitraeumtgeradelmenhorstal" + - "banshimonitayanagithubusercontentrentino-altoadigehjartdalhjelme" + - "landholeckobierzyceholidayhomeiphiladelphiaareadmyblogspotrentin" + - "o-s-tirolhomelinkitoolsztynsettlershimonosekikawahomelinuxn--1lq" + - "s03nhomeofficehomesecuritymacaparecidahomesecuritypchoshibuyacht" + - "sannohelplfinancialubindalublindesnesanokarumaifashionissayokkai" + - "chiropractichernivtsiciliahomesensellsyourhomegoodshimosuwalkis-" + - "a-nascarfanhomeunixn--1lqs71dhondahongotembaixadahonjyoitakasago" + - "tpantheonsitehornindalhorsembokukitashiobarahorteneis-a-nursemin" + - "ehospitalhoteleshimotsukehotmailhoyangerhoylandetroitskddielddan" + - "uorrikuzentakatajimidorissagamiharahumanitieshimotsumahurdalhuru" + - "majis-a-painteractivegaskvollhyllestadhyogoris-a-patsfanhyugawar" + - "ahyundaiwafuneis-very-sweetpepperis-with-thebandoisleofmancheste" + - "rjewelryjewishartgalleryjfkhakassiajgorajlljmphoenixn--1qqw23ajn" + - "jcphilipsyno-dshintokushimajoyentrentino-sued-tiroljoyokaichibal" + - "atinoipirangamvikharkivalleeaosteinkjerusalembroideryjpnjprshiny" + - "oshitomiokamitondabayashiogamagoriziajurkoseis-a-playerkosherbro" + - "okegawakoshimizumakiyosunndalkoshunantankhersonkosugekotohiradom" + - "ainsurehabmerkotourakouhokutamakizunokunimimatakatoris-a-republi" + - "cancerresearchaeologicaliforniakounosupplieshirahamatonbetsurnad" + - "alkouyamashikekouzushimashikis-a-rockstarachowicekozagawakozakis" + - "-a-socialistdlibestadkozowinnershirakofuefukihaboromskogkpnkppsp" + - "dnshiranukamitsuekrasnikahokutokashikis-a-soxfankrasnodarkredsto" + - "nekristiansandcatshiraois-a-studentalkristiansundkrodsheradkroks" + - "tadelvaldaostathelleluxembourgkryminamitanekumatorinokumejimasoy" + - "kumenantokigawakunisakis-a-teacherkassyncloudkunitachiarailwayku" + - "nitomigusukumamotoyamashikokuchuokunneppubtlshiraokamogawakunsts" + - "ammlungkunstunddesignkuokgroupictetrentino-suedtirolkureisenkurg" + - "ankurobelaudibleasingleshiratakahagitlaborkurogiminamiashigaraku" + - "roisoftwarendalenugkuromatsunais-a-techietis-a-personaltrainerku" + - "rotakikawasakis-a-therapistoiakushirogawakustanais-an-accountant" + - "shinichinankusupplykutchanelkutnokuzumakis-an-actorkvafjordkvals" + - "undkvamlidlugolekadenagahamaroygardenebakkeshibechambagriculture" + - "nnebudejjuedischesapeakebayernutwentekvanangenkvinesdalkvinnhera" + - "dkviteseidskogkvitsoykwpspectruminamiuonumassa-carrara-massacarr" + - "aramassabusinessebyklecznagasukekzmisugitokorozawamitourismolang" + - "evagrigentomologyeonggiehtavuoatnadexetermitoyoakemiuramiyazurew" + - "ebsiteshikagamiishibukawamiyotamanomjondalenmlbfanmonstermontrea" + - "lestatefarmequipmentrentinoaadigemonza-brianzapposhishikuis-an-a" + - "rtistgorymonza-e-della-brianzaptokuyamatsumotofukemonzabrianzara" + - "monzaebrianzamonzaedellabrianzamoonscalewismillermoparachutingmo" + - "rdoviamoriyamatsunomoriyoshiminamiawajikis-an-engineeringmormonm" + - "outhagakhanamigawamoroyamatsusakahoginankokubunjis-an-entertaine" + - "rmortgagemoscowioshisognemoseushistorymosjoenmoskeneshisuifuelve" + - "ruminamiyamashirokawanabelembetsukubankhmelnitskiyamarylhurstjor" + - "dalshalsenmosshitaramamosvikhmelnytskyivanylvenicemoteginowaniih" + - "amatamakawajimanxn--2scrj9christiansburgroks-thisayamanobeokakud" + - "amatsuemoviemovimientokyotangovtrentinoalto-adigemovistargardmoz" + - "illa-iotrentinoaltoadigemtranbymuenstermuginozawaonsenmuikamisat" + - "okaizukamikitayamatsuris-bytomaritimekeepingmukodairamulhouserve" + - "game-servermunakatanemuncienciamuosattemupictureshizukuishimofus" + - "aitamatsukuris-certifieducatorahimeshimamateramobaramurmanskhpla" + - "ystationmurotorcraftrentinos-tirolmusashimurayamatsushigemusashi" + - "noharamuseetrentinostirolmuseumverenigingmusicargodaddyn-o-saurl" + - "andeshizuokanagawamutsuzawamy-vigorgemy-wanggouvichristmasakindl" + - "efrakkestadyndns-picsantacruzsantafedjejuifminamidaitomandalucan" + - "iamyactivedirectorymyasustor-elvdalmycdn77-sslattuminanomydattol" + - "ocalhistorymyddnskingmydissentrentinosud-tirolmydobisshikis-foun" + - "dationmydroboehringerikemydshoppingmyeffectrentinosudtirolmyfire" + - "wallonieruchomoscienceandindustrynmyfritzmyftpaccesshoujis-gonem" + - "yhome-servermyjinomykolaivaomymailermymediapchromedicaltanissett" + - "airavennagatorockartuzymyokohamamatsudamypepiemontemypetshowamyp" + - "hotoshibalena-deviceshowtimembershriramsterdamnserverbaniamypiag" + - "etmyiphostrodawaramypsxn--30rr7ymysecuritycamerakermyshopblocksi" + - "enarutolgamytis-a-bookkeeperugiamytuleapilotsigdalmyvnchryslermy" + - "wirepaircraftingvollombardiamondsilklabudhabikinokawabarthaebaru" + - "ericssonyoursidell-ogliastraderpiwatepixolinopizzapknx-serversai" + - "lleshiojirishirifujiedaplantationplantsimple-urlplatformshangril" + - "ansirdalplazaplcube-serversicherungplumbingoplurinacionalpodhale" + - "zajskolobrzegersundpodlasiellaktyubinskiptveterinaireadthedocsca" + - "ppgafannefrankfurtrentinosuedtirolpodzonepohlpoivronpokerpokrovs" + - "komaganepoliticarrierpolitiendapolkowicepoltavalle-aostatic-acce" + - "ssjcbnpparibaselburgpomorzeszowitdkomakiyosemiteponpesaro-urbino" + - "-pesarourbinopesaromasvuotnaritakurashikis-into-animeguroroshinj" + - "ukumanowtvallee-d-aosteigenponypordenonepornporsangerporsangugep" + - "orsgrunnanyokoshibahikariwanumatakinouepoznanpraxis-a-bruinsfanp" + - "rdpreservationpresidioprgmrprimelhusdecorativeartslupskomatsushi" + - "masfjordenprincipeprivatizehealthinsuranceprochowiceproductionsl" + - "zprofesionalprogressivenneslaskerrylogisticsnoasaitoshimayfirsto" + - "ckholmestrandpromombetsurgeonshalloffameldalpropertyprotectionpr" + - "otonetrentinsud-tirolprudentialpruszkowithgoogleapisa-hockeynuts" + - "iracusakatakkoebenhavnprvcyberlevagangaviikanonjis-into-carshink" + - "amigotoyohashimototalprzeworskogptplusgardenpulawypupimientaketo" + - "misatomobellevuelosangelesjabbottrentinosued-tirolpvhagebostadpv" + - "trentinsudtirolpwchungnamdalseidfjordyndns-remotewdyndns-serveri" + - "signissedaluccapitalonewportlligatoyourapzqldqponiatowadaqslingq" + - "ualifioappinkmpspbarefootballfinanzgoraustinnavuotnaples3-ca-cen" + - "tral-1quickconnectrentinsued-tirolquicksytestingquipelementsokan" + - "aniimihoboleslawiechurcharternidyndns-webhopencraftoystre-slidre" + - "ttozawaqvcircleverappsseljeepsongdalenviknaharimalopolskanlandyn" + - "dns-wikirkenesantamariakesusonosuzakanazawasuzukaneyamazoesuzuki" + - "s-leetrentino-stirolsvalbardunloppacificircustomersveiosvelvikom" + - "onowruzhgorodeosvizzerasvn-reposopotrentinsuedtirolswedenswidnic" + - "artoonartdecologiaswidnikkokaminokawanishiaizubangeswiebodzin-bu" + - "tterswiftcoverswinoujscienceandhistoryswissmarterthanyousynology" + - "-diskstationsynology-dsor-odaltuscanytushuissier-justicetuvalle-" + - "daostavangertuxfamilytwmailvestfoldvestnesorocabalsan-sudtirolla" + - "gdenesnaaseinet-freaksolognevestre-slidreplanetariuminiserverves" + - "tre-totennishiawakuravestvagoyvevelstadvibo-valentiavibovalentia" + - "videovillasorreisahayakawakamiichikawamisatottoris-into-cartoons" + - "hinshinotsurgeryvinnicasacamdvrcampinagrandebuilderschlesischeso" + - "rtlandvinnytsiavipsinaappioneervirginiavirtual-userveexchangevir" + - "tualservervirtualuserveftpippugliavirtueeldomein-vigorlicevirtue" + - "lvisakegawaviterboknowsitallvivolkenkundenvixn--32vp30haibarakit" + - "ahiroshimapartmentsevenassisicilyvlaanderenvladikavkazimierz-dol" + - "nyvladimirvlogintoyonezawavminnesotaketakayamasudavologdanskomvu" + - "xn--2m4a15evolvolkswagentsorumincomcastresindevicenzaporizhzhiav" + - "olyngdalvoorloperauniterois-lostrolekamakurazakiwakunigamiharusl" + - "ivinghistoryvossevangenvotevotingvotoyonowloclawekongsbergwmflab" + - "soundcastronomy-routerwnextdirectromsakakinokiaworldworse-thanda" + - "wowithyoutuberspacekitagatargets-itroandinosaurepbodynamic-dnsor" + - "-varangerwpdevcloudwritesthisblogsytewroclawiwatsukiyonotairesta" + - "urantrogstadwtcmintelligencewtfastvps-serveronakasatsunairguardi" + - "annakadomarinebraskauniversitydalaheadjudaicable-modemocraciawuo" + - "zuwzmiuwajimaxn--3oq18vl8pn36axn--3pxu8kongsvingerxn--42c2d9axn-" + - "-45br5cylxn--45brj9citadeliveryggeexn--45q11citichernovtsymantec" + - "hnologyxn--4gbriminingxn--4it168dxn--4it797koninjambylxn--4pvxs4" + - "allxn--54b7fta0ccivilaviationiyodogawaxn--55qw42gxn--55qx5dxn--5" + - "js045dxn--5rtp49civilisationxn--5rtq34konskowolayangrouphonefoss" + - "hioyanaizustkannamilanotogawaxn--5su34j936bgsgxn--5tzm5gxn--6btw" + - "5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264civilizationxn--" + - "80adxhksouthcarolinarvikommunalforbundxn--80ao21axn--80aqecdr1ax" + - "n--80asehdbarrel-of-knowledgemologicallillyomitanoddaustrheimatu" + - "nduhrennesoyokozebinordreisa-geekaracoldwarmiastagebizenakanotod" + - "denayorovnobninskaragandaukraanghkeymachineustarnbergjemnes3-ap-" + - "southeast-2xn--80aswgxn--80augustownproviderxn--8ltr62konsulatro" + - "beepilepsykkylvenetodayxn--8pvr4uxn--8y0a063axn--90a3academiamic" + - "aaarborteaches-yogasawaracingxn--90aeroportalabamagasakishimabar" + - "aogakibichuoxn--90aishobarakawagoexn--90azhytomyravendbarrell-of" + - "-knowledgeologyonagoyauthordalandeportenrightathomeftpalmaserati" + - "bmdevelopmentcp4lima-cityeatselinogradultateshinanomachimkentate" + - "yamabogadodgehirnrtatsunobihirosakikamijimatsuzaki234xn--9dbhblg" + - "6dietcimdbarsycenterprisesakikugawalmartjeldsundishakotanflfanfs" + - "hostrowwlkpmgladefinimakanegasakirautomotiveconomiasakuchinotsuc" + - "hiurakawalesundgcagliaribeiraokinawashirosatochigiessensiositele" + - "kommunikationionjukudoyamaintenancehimejiitatebayashiibajddarchi" + - "tecturealtorlandiscountyolasitempresashibetsukuiiyamanouchikuhok" + - "uryugasakitaurayasudaurskog-holandingjerdrumemsettsupportaxihuan" + - "aval-d-aosta-valleyokosukanumazuryokoteastcoastaldefenceatonsber" + - "gjerstadotsuruokakamigaharagrocerybnikeisenbahnatuurwetenschappe" + - "naumburggfarmerseine164-baltimore-og-romsdalipayboltattoobiraetn" + - "abudapest-a-la-masion-webhostingdyniabruzzoologicalvinklein-addr" + - "ammenuorochesterimo-i-ranaamesjevuemielno-ipifonyc66xn--9dbq2axn" + - "--9et52uxn--9krt00axn--andy-iraxn--aroport-byandexcloudxn--asky-" + - "iraxn--aurskog-hland-jnbarsyonlinewhampshirealtysnes3-us-east-2x" + - "n--avery-yuasakuhokkaidownloadxn--b-5gaxn--b4w605ferdxn--balsan-" + - "sdtirol-nsbsouthwestfalenxn--bck1b9a5dre4civilwarmanagementozsde" + - "ltaiwanairforcebetsuikidsmynasushiobarackmazerbaijan-mayendofthe" + - "internetlifyis-a-cubicle-slavellinodearthachiojiyaitakanabeautys" + - "vardoesntexisteingeekashibatakasugais-a-democratrani-andria-barl" + - "etta-trani-andriaxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jx" + - "axn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn-" + - "-bievt-0qa2xn--bjarky-fyaotsurreyxn--bjddar-ptarnobrzegyptianxn-" + - "-blt-elabourxn--bmlo-graingerxn--bod-2natalxn--bozen-sdtirol-2ob" + - "anazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accid" + - "ent-investigation-aptibleadpagest-mon-blogueurovision-rancherkas" + - "ydneyxn--brum-voagatromsojamisonxn--btsfjord-9zaxn--bulsan-sdtir" + - "ol-nsbashkiriautoscanadaeguambulanceoceanographics3-eu-west-1xn-" + - "-c1avgxn--c2br7gxn--c3s14misasaguris-an-anarchistoricalsocietyxn" + - "--cck2b3basicservercelliguriaveroykenglandiscourses3-eu-west-2xn" + - "--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-not-certi" + - "fiedugit-pagespeedmobilizeroticahcesuoloanshinshiroxn--ciqpnxn--" + - "clchc0ea0b2g2a9gcdxn--comunicaes-v6a2oxn--correios-e-telecomunic" + - "aes-ghc29axn--czr694basilicataniavocatanzarowebspacemreviewskrak" + - "oweddingjesdalazioceanographiqueu-1xn--czrs0trusteexn--czru2dxn-" + - "-czrw28basketballyngenvironmentalconservationrenderxn--d1acj3bat" + - "ochiokinoshimakeupowiat-band-campaniavoues3-eu-west-3utilitiesqu" + - "are7xn--d1alfaromeoxn--d1atrvareservehalflifestylexn--d5qv7z876c" + - "lanbibaidarmeniaxn--davvenjrga-y4axn--djrs72d6uyxn--djty4konyvel" + - "olipophotographysioxn--dnna-grajewolterskluwerxn--drbak-wuaxn--d" + - "yry-iraxn--e1a4cldmailovecollegefantasyleaguernseyxn--eckvdtc9dx" + - "n--efvn9sowaxn--efvy88hair-surveillancexn--ehqz56nxn--elqq16haka" + - "tanortonxn--estv75gxn--eveni-0qa01gaxn--f6qx53axn--fct429kooris-" + - "a-photographerokuapphilatelyxn--fhbeiarnxn--finny-yuaxn--fiq228c" + - "5hspeedpartnersolundbeckomforbargainstantcloudfrontdoorxn--fiq64" + - "batsfjordisrechtraininglassassinationalheritageu-4xn--fiqs8spjel" + - "kavikommunexn--fiqz9spreadbettingxn--fjord-lraxn--fjq720axn--fl-" + - "ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbsspydebergxn--forlc" + - "esena-c8axn--fpcrj9c3dxn--frde-grandrapidsrlxn--frna-woaraisaijo" + - "soyrovigotsukitahatakamoriokakegawaxn--frya-hraxn--fzc2c9e2click" + - "ashiharaxn--fzys8d69uvgmailxn--g2xx48clinichirurgiens-dentistes-" + - "en-francexn--gckr3f0fauskedsmokorsetagayaseralingenoamishirasato" + - "gokasells-for-lessaskatchewanxn--gecrj9cliniquenoharaxn--ggaviik" + - "a-8ya47hakodatexn--gildeskl-g0axn--givuotna-8yasakaiminatoyookan" + - "iepcexn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-savedu" + - "netflixilxn--gmqw5axn--h-2failxn--h1aeghakonexn--h2breg3evenesrt" + - "rentoyonakagyokutoyakokonoexn--h2brj9c8clintonoshoesantoandreamh" + - "ostersanukis-a-designerxn--h3cuzk1digitalxn--hbmer-xqaxn--hcesuo" + - "lo-7ya35bauhausposts-and-telecommunicationswatch-and-clockerxn--" + - "hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4acciden" + - "t-prevention-riopretobamaceratabuseating-organicbcn-north-1xn--h" + - "nefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn-" + - "-hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery" + - "-fyasugivingxn--io0a7is-slickfhappousrcfastlylbananarepublicasad" + - "elamonedatingjovikarasjohkamikoaniikappuboliviajessheimetacentru" + - "meteorappalmspringsakerevistaples3-external-1xn--j1aefbsbxn--12c" + - "fi8ixb8lxn--j1amhakubahccavuotnagarahkkeravjuegoshikikuchikuseik" + - "arugalsacexn--j6w193gxn--jlq61u9w7beneventoeidsvollimanowarudaxa" + - "ustevollavagiskebinagisoccertmgretakahamalselvendrellavangenavig" + - "ationavoizumizakibigawajudygarlanddnslivelanddnss3-ap-southeast-" + - "1kappchizip6xn--jlster-byasuokanoyaltakashimarshallstatebankoper" + - "vikharkovalleedaostexn--jrpeland-54axn--jvr189misawaxn--k7yn95ex" + - "n--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woax" + - "n--klt787dxn--kltp7dxn--kltx9axn--klty5xn--3bst00misakis-an-actr" + - "esshinjournalismailillesandefjordxn--koluokta-7ya57hakuis-a-lawy" + - "erxn--kprw13dxn--kpry57dxn--kpu716fbx-osassaris-a-doctorayxn--kp" + - "ut3is-uberleetrentino-sud-tirolxn--krager-gyatomitamamuraxn--kra" + - "nghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfas" + - "tpanelblagrarchaeologyeongbuk0emmafann-arboretumbriamallamaceiob" + - "bcg120001wwwebredirectmemorial-o-g-i-n4t3l3p0rtashkentatamotors3" + - "-ap-northeast-1337xn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyatsuk" + - "anraxn--kvnangen-k0axn--l-1fairwindsrvaporcloudxn--l1accenturekl" + - "amborghinikolaeventstorfjordxn--laheadju-7yatsushiroxn--langevg-" + - "jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52bentleyonagunicomm" + - "bankarasjokarasuyamarugame-hostrowiecaseihicampobassociatest-ise" + - "rvecounterstrikeverbankaratevje-og-hornnes3-us-gov-west-1xn--les" + - "und-huaxn--lgbbat1ad8jelenia-goraxn--lgrd-poacctrycloudflarezzox" + - "n--lhppi-xqaxn--linds-pramericanartrysiljanxn--lns-qlanxesstorjd" + - "evcloudfunctionshintomikasaharaxn--loabt-0qaxn--lrdal-sraxn--lre" + - "nskog-54axn--lt-liaclothingdustdataitogitsuldalucernexn--lten-gr" + - "anexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2dde" + - "stpetersburgxn--mgb9awbfbxosaudaxn--mgba3a3ejtunesorfoldxn--mgba" + - "3a4f16axn--mgba3a4franamizuholdingstreamuneuesolutionsokndalxn--" + - "mgba7c0bbn0axn--mgbaakc7dvfedorapeoplegnicanonoichinomiyakexn--m" + - "gbaam7a8hakusanagochijiwadellogliastradingxn--mgbab2bdxn--mgbah1" + - "a3hjkrdxn--mgbai9a5eva00beppublishproxyzgorzeleccoffeedbackplane" + - "applicationcloudaccesscambridgestonewyorkshirecifedexhibitionhkt" + - "jmaxxxboxenapponazure-mobilexn--mgbai9azgqp6jeonnamerikawauexn--" + - "mgbayh7gpaleoxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbe" + - "rp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mg" + - "bpl2fhskydivingxn--mgbqly7c0a67fbcn-northwest-1xn--mgbqly7cvafra" + - "nziskanerimaringatlantakahashimamakiryuohdattowebcampinashikimin" + - "ohostre-totendofinternet-dnsaliasiaxn--mgbt3dhdxn--mgbtf8flatang" + - "erxn--mgbtx2beskidyn-ip24xn--mgbx4cd0abbvieeexn--mix082fedorapro" + - "jectravelchannelxn--mix891feiraquarelleaseeklogesauheradynserveb" + - "bsasebofageorgeorgiaxn--mjndalen-64axn--mk0axin-dslgbtunkomorots" + - "ukaminoyamaxunjargaxn--mk1bu44cngrondarxn--mkru45is-very-badajoz" + - "xn--mlatvuopmi-s4axn--mli-tlapyxn--mlselv-iuaxn--moreke-juaxn--m" + - "ori-qsakuragawaxn--mosjen-eyawaraxn--mot-tlaquilancomelbournexn-" + - "-mre-og-romsdal-qqbestbuyshouses3-us-west-1xn--msy-ula0haldenxn-" + - "-mtta-vrjjat-k7aflakstadaokagakicks-assnasaarlandxn--muost-0qaxn" + - "--mxtq1misconfusedxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--3ds443gxn-" + - "-nit225koryokamikawanehonbetsurutaharaxn--nmesjevuemie-tcbalsan-" + - "suedtirolkuszczytnombresciaxn--nnx388axn--nodessakurais-very-evi" + - "llagexn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nt" + - "tery-byaeservehttpiszxn--nvuotna-hwaxn--nyqy26axn--o1achattanoog" + - "anordlandxn--o3cw4halsaintlouis-a-anarchistoireggiocalabriaxn--o" + - "3cyx2axn--od0algxn--od0aq3betainaboxfusejnyoriikaratsuginamikata" + - "gamilitaryoshiokanzakiyokawaraxn--ogbpf8flekkefjordxn--oppegrd-i" + - "xaxn--ostery-fyawatahamaxn--osyro-wuaxn--otu796dxn--p1acfermochi" + - "zukirovogradoyxn--p1ais-very-goodyearxn--pbt977cnpyatigorskodjef" + - "fersonxn--pgbs0dhlxn--porsgu-sta26ferraraxn--pssu33lxn--pssy2uxn" + - "--q9jyb4cnsaobernardoxn--qcka1pmckinseyxn--qqqt11mishimatsumaeba" + - "shikshacknetrentinoa-adigexn--qxamusementdllpagestudioxn--rady-i" + - "raxn--rdal-poaxn--rde-ularvikosaigawaxn--rdy-0nabaris-very-nicex" + - "n--rennesy-v1axn--rhkkervju-01aferrarivnexn--rholt-mragowoodside" + - "moneyxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5nativeame" + - "ricanantiquestudynamisches-dnsomaxn--risr-iraxn--rland-uuaxn--rl" + - "ingen-mxaxn--rmskog-byaxn--rny31hammarfeastafricapetownnews-stag" + - "ingxn--rovu88bhzcasertaipeiheijin-the-bandain-vpncasinordkappana" + - "matta-varjjatjomemergencyahikobeardubaiduckdns3-us-west-2xn--rro" + - "s-granvindafjordxn--rskog-uuaxn--rst-0naturalhistorymuseumcenter" + - "xn--rsta-francaiseharaxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byax" + - "n--s-1faithruherecipescaravantaarpittsburghofficialxn--s9brj9cnt" + - "raniandriabarlettatraniandriaxn--sandnessjen-ogbieigersundivtasv" + - "uodnakamuratajirittogliattires3-website-ap-northeast-1xn--sandy-" + - "yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-gratangenxn--" + - "skierv-utazastuff-4-salexn--skjervy-v1axn--skjk-soaxn--sknit-yqa" + - "xn--sknland-fxaxn--slat-5naturalsciencesnaturellestufftoread-boo" + - "ksnesomnarviikamishihoronobeauxartsandcraftsolarssonxn--slt-elab" + - "cieszynxn--smla-hraxn--smna-gratis-a-bulls-fanxn--snase-nraxn--s" + - "ndre-land-0cbielawaltervistaprinternationalfirearms3-website-ap-" + - "southeast-1xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-" + - "q1axn--sr-odal-q1axn--sr-varanger-ggbielladbrokes3-website-ap-so" + - "utheast-2xn--srfold-byaxn--srreisa-q1axn--srum-grazxn--stfold-9x" + - "axn--stjrdal-s1axn--stjrdalshalsen-sqbieszczadygeyachiyodaejeonb" + - "uklugsmilebtimnetzjampagefrontappanasonicateringebuildingleezexn" + - "--stre-toten-zcbievathletajimabaridagawakuyabukijobserverrankosh" + - "igayachimataijincheonhlfanhs3-website-eu-west-1xn--t60b56axn--tc" + - "kweatherchannelxn--tiq49xqyjetztrentino-sudtirolxn--tjme-hraxn--" + - "tn0agrinetbankosakaerodromegallupinbarreauctionredumbrella-spezi" + - "australiaisondriobranconagawalbrzycharitysfjordds3-eu-central-1x" + - "n--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbif" + - "ukagawarszawashingtondclkareliancexn--trentin-sdtirol-7vbigv-inf" + - "oodnetworkangerxn--trentino-sd-tirol-c3bihorologyukindianapolis-" + - "a-bloggerxn--trentino-sdtirol-szbikedagestangeometre-experts-com" + - "ptables3-website-sa-east-1xn--trentinosd-tirol-rzbilbaogashimada" + - "chicagoboats3-website-us-east-1xn--trentinosdtirol-7vbillustrati" + - "onthewifiatmallorcadaques3-website-us-west-1xn--trentinsd-tirol-" + - "6vbiohtawaramotoineppueblockbustermezlglitchaselfiparaglidingliw" + - "icexn--trentinsdtirol-nsbirdartcenterprisecloudappspotagerxn--tr" + - "gstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvarggatr" + - "itonxn--uc0ay4axn--uist22hamurakamigoris-a-liberalxn--uisz3gxn--" + - "unjrga-rtarumizusawaxn--unup4yxn--uuwu58axn--vads-jraxn--valle-a" + - "oste-ebbturystykanmakiwienxn--valle-d-aoste-ehbodollstuttgartrev" + - "isohughesooxn--valleaoste-e7axn--valledaoste-ebbvacationsusakis-" + - "into-gamessinazawaxn--vard-jraxn--vegrshei-c0axn--vermgensberate" + - "r-ctbirkenesoddtangenovaraholtalenikonanporomutashinaindianmarke" + - "tinglobalashovhachinohedmarkariyameiwamarumorimachidaxn--vermgen" + - "sberatung-pwbirthplacexn--vestvgy-ixa6oxn--vg-yiabkhaziaxn--vgan" + - "-qoaxn--vgsy-qoa0jevnakershuscultureggioemiliaromagnamsosnowiech" + - "oyodobashichikashukujitawaraumalborkasaokamiminersantabarbaraxn-" + - "-vgu402coguchikuzenxn--vhquvaroyxn--vler-qoaxn--vre-eiker-k8axn-" + - "-vrggt-xqadxn--vry-yla5gxn--vuq861bjarkoyukuhashimoichinosekigah" + - "araxn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1collection" + - "xn--wgbl6axn--xhq521bjerkreimbamblebesbyglandroverhallaakesvuemi" + - "elecceu-2xn--xkc2al3hye2axn--xkc2dl3a5ee0handsonxn--y9a3aquarium" + - "issilevangerxn--yer-znaturbruksgymnxn--yfro4i67oxn--ygarden-p1ax" + - "n--ygbi2ammxn--3e0b707exn--ystre-slidre-ujbjugnieznord-frontierx" + - "n--zbx025dxn--zf0ao64axn--zf0avxn--3hcrj9cistrondheimmobilieniss" + - "hingucciprianiigataishinomakinkobayashikaoirmitakeharaxn--zfr164" + - "blackfridayurihonjournalistjohninohekinannestadivttasvuotnakanii" + - "kawatanaguraxnbayxz" + "dingen4tatarantours3-ap-south-16-b-dataiji234lima-cityeatselinog" + + "radult3l3p0rtashkentatamotors3-ap-northeast-2038blackfridayuu2-l" + + "ocalhostoregontrailroadnparachutingleezebloombergbauernirasakind" + + "igenaklodzkochikushinonsenergyuzawabloxcms3-website-us-west-1blu" + + "edagestangemologicallimoliseminebmoattachments3-website-us-west-" + + "2bms5ybmweddinglitchattanooganordlandrangedalinkyard-cloudyclust" + + "erbnrwedeploybomloabathsbchernihivgubsakyotanabellunord-aurdalph" + + "a-myqnapcloudaccesscambridgestoneuesalangenishiazaindustriabondr" + + "ay-dnsupdaternopilawatchesalondonetskaruizawabonnishigohtawaramo" + + "toineppueblockbustermezparaglidingliwicebookinghostfoldnavybooml" + + "air-traffic-controlleyboschaefflerdalivornomutashinaindustrieste" + + "amfamberkeleybostikarumaifarmsteadrayddnsfreebox-osascoli-piceno" + + "rdre-landraydnsaltdalombardynaliaskimitsubatamibudejjuegoshikiho" + + "kumakogenebakkeshibechambagriculturennebugattiffanynysadoes-itve" + + "destrandrivefsnillfjordrobaknoluoktachikawakembuchikumagayagawak" + + "kanaibetsubamericanfamilydsclouderackmazerbaijan-mayen-rootaribe" + + "iraogashimadachicagoboatsaludrudupontariobranconakamuratajirivne" + + "bostonakijinsekikogentappsselfiparisor-fronishiharabotanicalgard" + + "enishiizunazukinfinitintuitjomeloyalistoragebotanicgardenishikat" + + "aketomisatomobellevuelosangelesjabbottjxfinitybotanybouncemerckm" + + "sdnipropetrovskjervoyageorgeorgiabounty-fullensakerrypropertiesa" + + "lvadordalibabalestrandabergamo-siemensncfdurbanamexnethnologybou" + + "tiquebechernivtsiciliabozen-sudtirolomzaporizhzhegurinuyamashina" + + "tsukigatakasakitaurayasudabozen-suedtirolondrinamsskoganeinvestm" + + "entsalzburglobalashovhachinohedmarkasaokamiminersamegawabplacedo" + + "gawarabikomaezakirunorddalorenskogloboavistanbulsan-sudtiroloten" + + "kawabrandywinevalleybrasiliabrindisibenikimobetsuitainaioiraseba" + + "stopologyeongnamegawafflecellclaimsamnangerbristoloseyouriparlia" + + "mentkmaxxjavald-aostarnberglogowegroweibolognagareyamakeupowiath" + + "letajimabaridagawakuyabukikonaikawachinaganoharamcoachampionship" + + "hoptobishimadridvagsoyerbritishcolumbialowiezaganishikatsuragit-" + + "reposampalacebroadcastleclerchernovtsymantechnologybroadwaybroke" + + "-itksatxn--0trq7p7nnishikawazukamisunagawabrokerbronnoysundurham" + + "burgloppenzaolbia-tempio-olbiatempioolbialystokkepnogatagajoboji" + + "nzais-a-candidatebrothermesaverdealstahaugesunderseaportsinfolld" + + "alottebrowsersafetymarketsamsclubartoweirbrumunddalottokonamegat" + + "akayamashikokuchuobrunelasticbeanstalkashibatakatoris-a-catererb" + + "russelsamsunglugmbhartipscbgminakamichiharabruxellesandnessjoeni" + + "shimerabryansklepparmatta-varjjatmparochernigovernmentoyosatoyok" + + "awabrynewjerseybuskerudinewmexicoalouvreitoyotaparsandoybuzentsu" + + "jiiebuzzwellbeingzonebwfarsundweberbzhitomirumalatvuopmicrolight" + + "ingmodellingmxn--11b4c3dynathomebuiltwithdarkashiharabzzcolumbus" + + "heycommunecommunity-prochowicecomoarekecomparemarkerryhotelsanta" + + "mariakecompute-1computerhistoryofscience-fictioncomsecuritytacti" + + "csantoandreamhostersanukis-a-cubicle-slavellinodearthachiojiyaho" + + "oguycondoshichinohealth-carereforminamidaitomanchesterconference" + + "constructionconsuladonnagatorodoyconsultanthropologyconsultingro" + + "ngausdalukowhalingrossetouchihayaakasakawaharacontactraniandriab" + + "arlettatraniandriacontagematsubaracontemporaryarteducationalchik" + + "ugodontexistmein-iservebeercontractorskenconventureshinodebalanc" + + "ertificationcookingchannelsdvrdnsfor-better-thanawassamukawatari" + + "ghtathomeftpartycooluroycooperativano-frankivskolegallocus-3cope" + + "nhagencyclopedichitosetogakushimotoganewyorkshirecifedexhibition" + + "ishinoomotegocorsicafederationcorvettemp-dnsaobernardocosenzakop" + + "anecosidnshome-webserverdalutskasuyameinforumzcostumedicinaharim" + + "alopolskanlandyndns-office-on-the-webhareidsbergentingroundhandl" + + "ingroznycouchpotatofriesaogoncarriercounciluxurycouponsaotomelda" + + "luzerncq-acranbrookuwanalyticsapporocrdyndns-picsardegnaroycredi" + + "tcardyndns-remotewdyndns-serverisigncreditunioncremonashgabadadd" + + "jaguarqcxn--12c1fe0bradescorporationrendercrewhoswhokksundyndns-" + + "webhopencraftranoycricketrzyncrimeast-kazakhstanangercrotonecrow" + + "nipasadenarashinocrsvpassagensardiniacruisesarlvivanovoldacrypto" + + "nomichigangwoncuisinellajollamericanexpressexyculturalcentertain" + + "mentransportecuneocupcakecuritibaghdadyndns-wikirkenesarpsborgrp" + + "assenger-associationcymrussiacyonabaruminamiechizencyouthruherec" + + "ipescaravantaarpatriaferrerotikagoshimalvikaszubyfetsundyndns1fg" + + "uidegreefhvalerfidoomdnstracefieldynnsarufutsunomiyawakasaikaita" + + "koelnfigueresinstaginguitarsauheradynservebbsasayamayfirstockhol" + + "mestrandyndns-workshopitsitexaskoyabearalvahkijobservableusercon" + + "tentransurlfilateliafilegear-audnedalnfilegear-deatnulminamiiser" + + "niafilegear-gbizfilegear-iefilegear-jpmorganfilegear-sgujohanama" + + "kinoharafilminamiizukamiokameokameyamatotakadafinalfinancefinear" + + "tsavannahgafinlandynufcfanfinnoyfirebaseapplinzis-a-doctorayfire" + + "nzefirestonefirmdalegoldpoint2thisamitsukefishingolffansaves-the" + + "-whalessandria-trani-barletta-andriatranibarlettaandriafitjarvod" + + "kafjordynv6fitnessettlementravelersinsurancefjalerflesbergulenfl" + + "ickragerogersavonarusawaflightsaxoflirfloginlinefloraflorenceflo" + + "ridattorelayfloripaderbornfloristanohatakaharulvikatowicefloroku" + + "nohealthcareerschoenbrunnflowerschokokekschokoladenfltrdynvpnplu" + + "s-4flynnhosting-clusterflynnhubarcelonagawalesundgcagliaricoharu" + + "ovataxihuanflfanfshostrowwlkpmgjerdrumemsettsupportcp4fndyroyrvi" + + "kingruefor-ourfor-somedizinhistorischescholarshipschoolschulefor" + + "-theaterforexrothachirogatakamoriokakudamatsueforgotdnschwarzgwa" + + "ngjuniperforli-cesena-forlicesenaforlikescandyn53forsaleikangerf" + + "orsandasuologoipaviancargodaddyn-o-saurlandeschweizfortalfortmis" + + "soulancasterfortworthadanorthwesternmutualfosnesciencecenterscie" + + "ncehistoryfotaruis-a-financialadvisor-aurdalfoxfordebianfozorafr" + + "edrikstadtvscientistordalfreeddnsgeekgalaxyfreedesktopocznore-og" + + "-uvdalfreemasonryfreesitextileirfjordfreetlscjohnsonfreiburgunma" + + "nxn--12co0c3b4evalleaostavangerfreightrentin-sud-tirolfreseniusc" + + "ountryestateofdelawareggio-calabriafribourguovdageaidnunusualper" + + "sonfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-" + + "giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-g" + + "iuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriuli" + + "vgiuliafrlfroganscotlandfrognfrolandfrom-akrehamnfrom-alfrom-arf" + + "rom-azimutheworkpccwiiheyakagefrom-capebretonamicrosoftbankatsus" + + "hikabeeldengeluidfrom-codyn-vpndnscrapper-sitefrom-ctrentin-sudt" + + "irolfrom-dchocolatelevisionishinoshimatsushigefrom-dedyn-berlinc" + + "olnfrom-flanderscrappingushikamifuranorth-kazakhstanfrom-gaulard" + + "alfrom-hichisochildrensgardenfrom-iafrom-idfrom-ilfrom-in-brbarc" + + "laycards3-sa-east-1from-kscrysechofunatoriginstitutemasekashiwaz" + + "akiyosatokamachintaifun-dnsdojolsterfrom-kyowariasahikawawildlif" + + "edorainfracloudfrontdoorfrom-lanciafrom-mamurogawafrom-mdfrom-me" + + "eresistancefrom-mifunefrom-mnfrom-modalenfrom-mserveirchonanbuls" + + "an-suedtirolowiczest-le-patronishiokoppegardyndns-at-homedepoten" + + "zamamidsundyndns-at-workisboringrimstadyndns-blogdnsangofrom-mtn" + + "from-nctulangevagrigentomologyeonggiehtavuoatnadexeterfrom-ndfro" + + "m-nefrom-nh-serveblogsiteleafamilycompanyminamimakis-a-geekatsuy" + + "amarugame-hostrowiechoseiroumuenchenishitosashimizunaminamibosog" + + "ndalpusercontentoyotsukaidofrom-njaworznoticiasnesoddenmarkhange" + + "lskjakdnepropetrovskiervaapsteiermarkaufenfrom-nminamiminowafrom" + + "-nvalled-aostavernfrom-nyfrom-ohkurafrom-oketogurafrom-orfrom-pa" + + "dovaksdalfrom-pratohmandalfrom-ris-a-greenfrom-schmidtre-gauldal" + + "from-sdfrom-tnfrom-txn--1ck2e1barclays3-us-east-2from-utazuerich" + + "ardlillehammerfeste-ipfizerfrom-val-daostavalleyfrom-vtrentin-su" + + "ed-tirolfrom-wafrom-wielunnerfrom-wvalledaostaobaomoriguchiharah" + + "kkeravjuedischesapeakebayernunzenfrom-wyfrosinonefrostalowa-wola" + + "wafroyahikobeardubaiduckdnserveminecraftrentin-suedtirolfstcgrou" + + "pgfoggiafujiiderafujikawaguchikonefujiminokamoenairguardiannakad" + + "omarineat-urlfujinomiyadavvenjargap-northeast-3fujiokayamangonoh" + + "ejis-a-guruslivinghistoryfujisatoshonairlinebraskauniversitychya" + + "ttorneyagawakayamagazinedre-eikerfujisawafujishiroishidakabirato" + + "ridefenseljordfujitsurugashimangyshlakasamatsudovre-eikerfujixer" + + "oxn--1ctwolominamatargivestbytemarkautokeinotteroyfujiyoshidavve" + + "siidatsunanjoburgwiddleitungsenfukayabeatservemp3fukuchiyamadaza" + + "ifudaigojomedio-campidano-mediocampidanomediofukudominichoshibuy" + + "achiyodatingripefukuis-a-hard-workerservep2pharmacienservepicser" + + "vequakefukumitsubishigakisarazurecontainerdpolicefukuokazakishiw" + + "adafukuroishikarikaturindalfukusakisofukushimaniwakuratefukuyama" + + "gatakahatakaishimogosenfunabashiriuchinadafunagatakamatsukawafun" + + "ahashikamiamakusatsumasendaisennangooglecodespotrentino-a-adigef" + + "undaciofuoiskujukuriyamannorfolkebibleirvikazoologyfuosskoczowil" + + "liamhillfurnitureggio-emilia-romagnakasatsunairportland-4-salern" + + "oboribetsuckservesarcasmatartanddesignfurubirafurudonostiaafuruk" + + "awairtelebitballooningxn--1lqs03nfusodegaurafussagamiharafutabay" + + "amaguchinomigawafutboldlygoingnowhere-for-morenakatombetsumitaka" + + "giizefuttsurugimperiafuturecmservicesevastopolefuturehostingfutu" + + "remailingfvgfylkesbiblackbaudcdn77-securebungoonord-odalwaysdata" + + "baseballangenkainanaejrietisalatinabenonicbcn-north-1fyresdalhan" + + "goutsystemscloudhannanmokuizumodenakayamapartmentsewinbarefootba" + + "llfinanzgoraustrheimatunduhrennesoyokozebinagisoccertmgretakaham" + + "alselvendrellaziobiramusementdllpages3-ap-southeast-2hannosegawa" + + "hanyuzenhapmirharstadharvestcelebrationhasamarburghasaminami-alp" + + "sharis-a-lawyerhashbanghasudahasura-appharmacysharphdfcbankddiel" + + "ddanuorrittogliattireshawaiijimaritimoduminamioguni5hasvikfhappo" + + "usrcfastly-terrariuminamifuranohatogayaitakanezawahatoyamazakita" + + "kamiizumisanofidelityhatsukaichikaiseis-a-liberalhattfjelldalhay" + + "ashimamotobungotakadancehazuminobusells-for-ustkannamilanotogawa" + + "helsinkitakatakaokalmykiahembygdsforbundhemneshellaspeziahemseda" + + "lhepforgeherokussldheroyhgtvallee-aosteroyhigashiagatsumagoiania" + + "higashichichibunkyonanaoshimageandsoundandvisionthewifiatmallorc" + + "adaqueshimojis-a-libertarianhigashihiroshimanehigashiizumozakita" + + "kyushuaiahigashikagawahigashikagurasoedahigashikawakitaaikitamih" + + "amadahigashikurumeetnedalhigashimatsushimarcheapigeelvinckhakass" + + "iahigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcyclesh" + + "imokawahigashinarusells-itrentino-alto-adigehigashinehigashiomih" + + "achimanagementrentino-altoadigehigashiosakasayamanakakogawahigas" + + "hishirakawamatakarazukaluganskygearapphiladelphiaareadmyblogspot" + + "rentino-s-tirolhigashisumiyoshikawaminamiaikitamotosumy-gatewayh" + + "igashitsunoshiroomurahigashiurausukitanakagusukumodernhigashiyam" + + "atokoriyamanashifteditchyouriphilatelyhigashiyodogawahigashiyosh" + + "inogaris-a-linux-useranishiaritabashijonawatehiraizumisatohnosho" + + "ooshikamaishimodatehirakatashinagawahiranairtrafficplexus-1hirar" + + "ahiratsukagawahirayaizuwakamatsubushikusakadogawahistorichousesh" + + "imokitayamahitachiomiyagildeskaliszhitachiotagoppdalhitraeumtger" + + "adelmenhorstalbanshimonitayanagithubusercontentrentino-stirolhja" + + "rtdalhjelmelandholeckobierzyceholidayhomeiphilipsyno-dshimonosek" + + "ikawahomelinkitoolsztynsettlershimosuwalkis-a-llamarriottrentino" + + "-sud-tirolhomelinuxn--1lqs71dhomeofficehomesecuritymacaparecidah" + + "omesecuritypchoyodobashichikashukujitawaravennagasukehomesenseer" + + "inghomeunixn--1qqw23ahondahongotembaixadahonjyoitakasagotpantheo" + + "nsitehornindalhorsellsyourhomegoodshimotsukehorteneis-a-musician" + + "hospitalhoteleshimotsumahotmailhoyangerhoylandetroitskypehumanit" + + "ieshinichinanhurdalhurumajis-a-nascarfanhyllestadhyogoris-a-nurs" + + "embokukitchenhyugawarahyundaiwafuneis-very-sweetpepperis-with-th" + + "ebandoisleofmanaustdaljewelryjewishartgalleryjfkharkovalleedaost" + + "ejgorajlljmphotographysiojnjcphonefosshintomikasaharajoyentrenti" + + "noa-adigejoyokaichibalatinogiftshiojirishirifujiedajpnjprshioyan" + + "aizujurkoseis-a-personaltrainerkosherbrookegawakoshimizumakizuno" + + "kunimimatakatsukiyosemitekoshunantankhmelnitskiyamarumorimachida" + + "kosugekotohiradomainsurehabmerkotourakouhokutamakis-a-photograph" + + "erokuapphoenixn--2m4a15ekounosupplieshirakofuefukihaboromskogkou" + + "yamarylhurstjordalshalsenkouzushimasfjordenkozagawakozakis-a-pla" + + "yerkozowindmillkpnkppspdnshiranukamitsuekrasnikahokutokashikis-a" + + "-republicancerresearchaeologicaliforniakrasnodarkredstonekristia" + + "nsandcatshiraois-a-rockstarachowicekristiansundkrodsheradkroksta" + + "delvaldaostarostwodzislawindowskrakowinnershiraokamogawakryminam" + + "isanrikubetsurfastpanelblagrarchaeologyeongbuk0emmafann-arboretu" + + "mbriamallamaceiobbcg120001wwwebredirectmembers3-ap-northeast-133" + + "7kumatorinokumejimashikis-a-socialistdlibestadkumenantokigawakun" + + "isakis-a-soxfankunitachiarailwaykunitomigusukumamotoyamashikekun" + + "neppubtlshiratakahagitlaborkunstsammlungkunstunddesignkuokgroupi" + + "lotshishikuis-a-studentalkureisenkurgankurobelaudibleasingleshis" + + "ognekurogiminamiashigarakuroisoftwarendalenugkuromatsunais-a-tea" + + "cherkassyncloudkurotakikawasakis-a-techietis-a-painteractivegask" + + "vollkushirogawakustanais-a-therapistoiakusupplykutchanelkutnokuz" + + "umakis-an-accountantshinjournalismailillesandefjordkvafjordkvals" + + "undkvamlidlugolekadenagahamaroygardendoftheinternetlifyis-an-act" + + "orkvanangenkvinesdalkvinnheradkviteseidskogkvitsoykwpspectrumina" + + "mitanekzmissileluxembourgmisugitokorozawamitourismolanxesshisuif" + + "uettertdasnetzmitoyoakemiuramiyazurewebsiteshikagamiishibukawami" + + "yotamanomjondalenmlbfanmonstermontrealestatefarmequipmentrentino" + + "aadigemonza-brianzapposhitaramamonza-e-della-brianzaptokuyamatsu" + + "maebashikshacknetrentinoalto-adigemonzabrianzaramonzaebrianzamon" + + "zaedellabrianzamoonscalevangermordoviamoriyamatsumotofukemoriyos" + + "himinamiawajikis-an-engineeringmormonmouthaebaruericssongdalenvi" + + "knakatsugawamoroyamatsunomortgagemoscowioshizukuishimofusaitamat" + + "sukuris-an-entertainermoseushistorymosjoenmoskeneshizuokanagawam" + + "osshoppingmosvikhplaystationmoteginowaniihamatamakawajimansionsh" + + "oujis-bytomaritimekeepingmoviemovimientokyotangovtrentinoaltoadi" + + "gemozilla-iotrentinos-tirolmtranbymuenstermuginozawaonsenmuikami" + + "satokaizukamikitayamatsuris-certifieducatorahimeshimamateramobar" + + "amukodairamulhouseoullensvanguardmunakatanemuncienciamuosattemup" + + "imientakinouemurmansklabudhabikinokawabarthadselectrentino-aadig" + + "emurotorcraftrentinostirolmusashimurayamatsusakahoginankokubunji" + + "s-foundationmusashinoharamuseetrentinosud-tirolmuseumverenigingm" + + "usicarbonia-iglesias-carboniaiglesiascarboniamutsuzawamy-vigorge" + + "my-wanggouvichromedicaltanissettairamyactivedirectorymyasustor-e" + + "lvdalmycdn77-sslattuminamiuonumassa-carrara-massacarraramassabus" + + "inessebyklecznagasakinderoymydattolocalhistorymyddnskingmydissen" + + "trentinosudtirolmydobisshikis-gonemydroboehringerikemydshowamyef" + + "fectrentinosued-tirolmyfirewallonieruchomoscienceandindustrynmyf" + + "oruminamiyamashirokawanabelembetsukubankhmelnytskyivanylvenicemy" + + "fritzmyftpaccesshowtimelhusdecorativeartshriramsterdamnserverban" + + "iamyhome-servermyjinomykolaivaomymailermymediapchungnamdalseidfj" + + "ordyndns-ipartis-a-chefashionishiwakis-a-conservativegarsheis-a-" + + "cpadualstackhero-networkinggroupartsannanissandiegomyokohamamats" + + "udamypepinkmpspbargainstantcloudfunctionswedenvironmentalconserv" + + "ationionjukudoyamaintenancempresashibetsukuiiyamanouchikuhokuryu" + + "gasakitashiobarauthordalanddnslivelanddnss3-eu-west-1mypetsienar" + + "utolgamyphotoshibalena-devicesigdalmypictetrentinosuedtirolmypsx" + + "n--30rr7ymysecuritycamerakermyshopblocksilknx-serverrankoshigaya" + + "nagawamytis-a-bloggermytuleapioneermyvnchurcharternidyndns-mailu" + + "bindalublindesnesannohelplfinancialucaniamywirepaircraftingvollo" + + "mbardiamondsimple-urlpizzapkolobrzegersundplantsirdalplatformsha" + + "ngrilapyplazaplcube-serversaillesjcbnpparibaselburgplumbingoplur" + + "inacionalpodhalewismillerpodlasiellaktyubinskiptveterinaireadthe" + + "docscappgafannefrankfurtrentinsud-tirolpodzonepohlpoivronpokerpo" + + "krovskomaganepoliticarrdpolitiendapolkowicepoltavalle-aostathell" + + "ezajskomakiyosunndalpomorzeszowitdkomatsushimarylandponpesaro-ur" + + "bino-pesarourbinopesaromasvuotnaritakurashikis-into-animeguroros" + + "hinkamigotoyohashimototalponypordenonepornporsangerporsangugepor" + + "sgrunnanyokoshibahikariwanumatakkoebenhavnpoznanpraxis-a-bookkee" + + "perspectakashimarnardalprdpreservationpresidioprgmrprimelbournep" + + "rincipeprivatizehealthinsuranceproductionslupskomforbarreauction" + + "-webhostingjerstadotsuruokakamigaharautomotiveconomiasakuchinots" + + "uchiurakawalbrzycharitysfjordds3-eu-west-2profesionalprogressive" + + "nneslaskerrylogisticslzpromombetsurgeonshalloffameiwamasoyproper" + + "typrotectionprotonetrentinsudtirolprudentialpruszkowithgoogleapi" + + "sa-hockeynutsiracusakatamayufuelveruminanoprvcyberlevagangaviika" + + "nonjis-into-carshinshinotsurgeryprzeworskogptplusgardenpulawypup" + + "ippugliapvhagakhanamigawapvtrentinsued-tirolpwcircustomer-ocimdb" + + "ananarepublicaseihicampobassociatest-iservecounterstrikehimejibm" + + "deportevadsobetsumidatlanticasertaipeiheijiitatebayashiibajddarc" + + "hitecturealtorlandevelopmentattoobservereviewsaintlouis-a-bruins" + + "fanayorovnoceanographics3-fips-us-gov-west-1pzqhagebostadqldqpon" + + "iatowadaqslingqualifioappiszquickconnectrentinsuedtirolquicksyte" + + "stingquipelementsnoasaitoshimattelekommunikationqvcistrondheimmo" + + "bilienissayokkaichiropractichirurgiens-dentistes-en-francesuzaka" + + "nazawasuzukaneyamazoesuzukis-leetrentino-sudtirolsvalbardunloppa" + + "cificitichiryukyuragifuchungbukharaumalborkashiwarasveiosvelviko" + + "morotsukaminoyamaxunjargasvizzerasvn-reposomnarviikamishihoronob" + + "eauxartsandcraftsokndalswidnicartoonartdecologiaswidnikkokaminok" + + "awanishiaizubangeswiebodzin-butterswiftcoverswinoujscienceandhis" + + "toryswissmarterthanyousynology-diskstationsynology-dsooturystyka" + + "nmakiwientuscanytushuissier-justicetuvalle-daostaticsor-varanger" + + "tuxfamilytwmailvestfoldvestnesorfoldvestre-slidreplantationvestr" + + "e-totennishiawakuravestvagoyvevelstadvibo-valentiavibovalentiavi" + + "deovillasorocabalsan-sudtirollagdenesnaaseinet-freaksolarssonvin" + + "nicasacamdvrcampinagrandebuilderschlesischesorreisahayakawakamii" + + "chikawamisatottoris-into-cartoonshinshirovinnytsiavipsinaappitts" + + "burghofficialvirginiavirtual-userveftpiwatevirtualservervirtualu" + + "servegame-servervirtueeldomein-vigorlicevirtuelvisakegawaviterbo" + + "knowsitallvivolkenkundenvixn--32vp30haibarakitahatakanabeautysva" + + "rdoesntexisteingeekazunow-dnsevenassisicilyvlaanderenvladikavkaz" + + "imierz-dolnyvladimirvlogintoyonezawavminiserversicherungvologdan" + + "skongsbergvolvolkswagentsortlandvolyngdalvoorloperauniterois-los" + + "trolekamakurazakiwakunigamiharutwentevossevangenvotevotingvotoyo" + + "nowloclawekongsvingerwmflabsorumincomcastresindevicenzaporizhzhi" + + "awnextdirectrogstadworldworse-thandawowithyoutuberspacekitagatar" + + "getmyiphostrodawarawpdevcloudwritesthisblogsytewroclawiwatsukiyo" + + "notairestaurantroandinosaurepbodynamic-dnsopotrentoyonakagyokuto" + + "yakokonoewtcminnesotaketakazakis-an-actresshinjukumanowtvallee-d" + + "-aosteigenwtfastvps-serveronakanotoddenwuozuwzmiuwajimaxn--3oq18" + + "vl8pn36axn--3pxu8koninjambylxn--42c2d9axn--45br5cylxn--45brj9civ" + + "ilisationisshinguccircleverappsanokasukabedzin-berlindasdaburxn-" + + "-45q11civilizationiyodogawaxn--4gbriminingxn--4it168dxn--4it797k" + + "onskowolayangroupictureshirahamatonbetsurnadalxn--4pvxs4allxn--5" + + "4b7fta0ccivilwarmiastagets-itozsdeltajimidorissagaeroclubmedecin" + + "cinnationwidealerxn--55qw42gxn--55qx5dxn--5js045dxn--5rtp49clanb" + + "ibaidarmeniaxn--5rtq34konsulatrobeepilepsykkylvenetodayxn--5su34" + + "j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlx" + + "n--7t0a264cldmailovecollegefantasyleaguernseyxn--80adxhksoundcas" + + "tronomy-routerxn--80ao21axn--80aqecdr1axn--80asehdbarrell-of-kno" + + "wledgeiseiyoichippubetsubetsugarugbyglandroverhalla-speziautosca" + + "nadaeguambulanceobninskaracoldwarszawaukraanghkeymachinewhampshi" + + "realtydalaskanittedallasalleangaviikaascolipicenodumemergencyach" + + "ts3-ca-central-1xn--80aswgxn--80augustownproviderxn--8ltr62konyv" + + "elolipopiemontexn--8pvr4uxn--8y0a063axn--90a3academiamicaaarbort" + + "eaches-yogasawaracingxn--90aeroportalabamagasakishimabaraogakibi" + + "chuoxn--90aishobarakawagoexn--90azhytomyravendbarsycenterprisesa" + + "kikuchikuseikarugamvikarasjokarasuyamarshallstatebankaratemrhclo" + + "udiscountyombolzano-altoadigeometre-experts-comptables3-us-west-" + + "1xn--9dbhblg6dietciprianiigataishinomakinkobayashikaoirmitakehar" + + "axn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byandexc" + + "loudxn--asky-iraxn--aurskog-hland-jnbarsyonlinewhollandiscourses" + + "3-us-west-2xn--avery-yuasakuhokkaidownloadxn--b-5gaxn--b4w605fer" + + "dxn--balsan-sdtirol-nsbsouthcarolinarvikommunexn--bck1b9a5dre4cl" + + "ickasumigaurawa-mazowszextraspace-to-rentalstomakomaibaraxn--bdd" + + "dj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--b" + + "hccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fy" + + "aotsurreyxn--bjddar-ptarnobrzegyptianxn--blt-elabourxn--bmlo-gra" + + "ingerxn--bod-2natalxn--bozen-sdtirol-2obanazawaxn--brnny-wuacade" + + "my-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibl" + + "eadpagest-mon-blogueurovision-k3southwestfalenxn--brum-voagatrom" + + "sakakinokiaxn--btsfjord-9zaxn--bulsan-sdtirol-nsbashkiriaveroyke" + + "ngerdalcesurancechirealmpmnavigationavoizumizakibigawaurskog-hol" + + "andingdyniaetnabudapest-a-la-masion-riopretobamaceratabuseating-" + + "organicasadelamonedapliernewspapereportateshinanomachimkentateya" + + "mabogadobeaemcloud66xn--c1avgxn--c2br7gxn--c3s14misakis-an-anarc" + + "historicalsocietyxn--cck2b3basicservercelliguriavocatanzarowebsp" + + "acebinordreisa-geekaragandaustevoll-o-g-i-natuurwetenschappenaum" + + "burggfarmerseine164-baltimore-og-romsdalipayboltatsunobihirosaki" + + "kamijimatsuuragrocerybnikeisenbahnaturhistorisches3-ap-southeast" + + "-1kappchizip6xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaf" + + "orl-i8axn--cg4bkis-not-certifiedugit-pagespeedmobilizeroticahces" + + "uoloanshintokushimaxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--comunicaes" + + "-v6a2oxn--correios-e-telecomunicaes-ghc29axn--czr694basilicatani" + + "avoues3-eu-west-3utilitiesquare7xn--czrs0tromsojamisonxn--czru2d" + + "xn--czrw28basketballyngenhktjeldsundiscoveryomitanoceanographiqu" + + "eu-1xn--d1acj3batochiokinoshimaizuruhrxn--d1alfaromeoxn--d1atrus" + + "teexn--d5qv7z876clinichitachinakagawashtenawdev-myqnapcloudeitys" + + "nesandvikcoromantovalle-d-aostatic-accessanfranciscofreakunemuro" + + "rangehirnrtoyotomiyazakis-a-celticsfanishinomiyashironoxn--davve" + + "njrga-y4axn--djrs72d6uyxn--djty4kooris-a-patsfanxn--dnna-grajewo" + + "lterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cliniquenoharaxn--ec" + + "kvdtc9dxn--efvn9sowaxn--efvy88hair-surveillancexn--ehqz56nxn--el" + + "qq16hakatanortonxn--estv75gxn--eveni-0qa01gaxn--f6qx53axn--fct42" + + "9kopervikhersonxn--fhbeiarnxn--finny-yuaxn--fiq228c5hspeedpartne" + + "rsolognexn--fiq64batsfjordishakotanhlfanhs3-website-ap-northeast" + + "-1xn--fiqs8spjelkavikomonowruzhgorodeoxn--fiqz9spreadbettingxn--" + + "fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-ce" + + "sena-fcbsspydebergxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grandr" + + "apidsrlxn--frna-woaraisaijosoyrovigotsukisosakitagawaxn--frya-hr" + + "axn--fzc2c9e2clintonoshoesantabarbaraxn--fzys8d69uvgmailxn--g2xx" + + "48clothingdustdataitogitsuldalucernexn--gckr3f0fauskedsmokorseta" + + "gayaseralingenoamishirasatogokasells-for-lessasebofageologyxn--g" + + "ecrj9cn-northwest-1xn--ggaviika-8ya47hakodatexn--gildeskl-g0axn-" + + "-givuotna-8yasakaiminatoyookaniepcexn--gjvik-wuaxn--gk3at1exn--g" + + "ls-elacaixaxn--gmq050is-savedunetflixilxn--gmqw5axn--h-2failxn--" + + "h1aeghakonexn--h2breg3evenesrvaporcloudxn--h2brj9c8cngroks-thisa" + + "yamanobeokakegawaxn--h3cuzk1digitalxn--hbmer-xqaxn--hcesuolo-7ya" + + "35bauhausposts-and-telecommunications3-website-ap-southeast-1xn-" + + "-hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accide" + + "nt-prevention-rancherkasydneyxn--hnefoss-q1axn--hobl-iraxn--holt" + + "len-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn" + + "--i1b6b1a6a2exn--imr513nxn--indery-fyasugivingxn--io0a7is-slickh" + + "arkivalleeaosteinkjerusalembroideryxn--j1aefbsbxn--12cfi8ixb8lxn" + + "--j1amhakubahccavuotnagaraholtalenglandxn--j6w193gxn--jlq480n2rg" + + "xn--jlq61u9w7beneventoeidsvollimanowarudaxaustinnaval-d-aosta-va" + + "lleyokosukanumazuryokoteastcoastaldefenceatonsbergjemnes3-eu-cen" + + "tral-1xn--jlster-byasuokanoyakumoldeloittenrikuzentakataiwanairf" + + "orcebetsuikidsmynasushiobaragusartstorfjordxn--jrpeland-54axn--j" + + "vr189misasaguris-an-artistgoryxn--k7yn95exn--karmy-yuaxn--kbrq7o" + + "xn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn" + + "--kltx9axn--klty5xn--3bst00mintelligencexn--koluokta-7ya57hakuis" + + "-a-hunterxn--kprw13dxn--kpry57dxn--kpu716fbx-osaskatchewanxn--kp" + + "ut3is-uberleetrentino-sued-tirolxn--krager-gyatomitamamuraxn--kr" + + "anghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfa" + + "stlylbanzaicloudcontrolledekagaminombresciaustraliajudaicable-mo" + + "democraciabruzzoologicalvinklein-addrammenuorochesterimo-i-ranaa" + + "mesjevuemielno-ipifonyc66xn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-" + + "fyatsukanraxn--kvnangen-k0axn--l-1fairwindstorjdevcloudnshinyosh" + + "itomiokamitondabayashiogamagoriziaxn--l1accentureklamborghinikol" + + "aeventstpetersburgxn--laheadju-7yatsushiroxn--langevg-jxaxn--lcv" + + "r32dxn--ldingen-q1axn--leagaviika-52bentleyonagoyaxn--lesund-hua" + + "xn--lgbbat1ad8jelenia-goraxn--lgrd-poacctrvareservehalflifestyle" + + "xn--lhppi-xqaxn--linds-pramericanartrycloudflarezzoxn--lns-qlaqu" + + "ilanstreamswatch-and-clockerxn--loabt-0qaxn--lrdal-sraxn--lrensk" + + "og-54axn--lt-liacnpyatigorskodjeffersonxn--lten-granexn--lury-ir" + + "axn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddestudioxn--mgb9" + + "awbfbxosassaris-a-democratrapaniizaxn--mgba3a3ejtrysiljanxn--mgb" + + "a3a4f16axn--mgba3a4franamizuholdingstudynamisches-dnsolundbeckom" + + "munalforbundxn--mgba7c0bbn0axn--mgbaakc7dvfedorapeoplegnicanonoi" + + "chinomiyakexn--mgbaam7a8hakusanagochijiwadell-ogliastraderxn--mg" + + "bab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00beppublishproxyzgorzelec" + + "coffeedbackplaneapplicationcloudappspotagerxn--mgbai9azgqp6jeonn" + + "amerikawauexn--mgbayh7gpaleoxn--mgbbh1a71exn--mgbc0a9azcgxn--mgb" + + "ca7dzdoxn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4" + + "ecexposedxn--mgbpl2fhskydivingxn--mgbqly7c0a67fbcnsantacruzsanta" + + "fedjejuifmetlifeinsurancexn--mgbqly7cvafranziskanerimaringatlant" + + "akahashimamakiryuohdattowebcampinashikiminohostre-totendofintern" + + "et-dnsaliasiaxn--mgbt3dhdxn--mgbtf8flatangerxn--mgbtx2beskidyn-i" + + "p24xn--mgbx4cd0abbvieeexn--mix082fedoraprojectravelchannelxn--mi" + + "x891feiraquarelleaseeklogesaudaxn--mjndalen-64axn--mk0axin-dslgb" + + "tunesor-odalxn--mk1bu44cntrani-andria-barletta-trani-andriaxn--m" + + "kru45is-very-badajozxn--mlatvuopmi-s4axn--mli-tlarvikoryokamikaw" + + "anehonbetsurutaharaxn--mlselv-iuaxn--moreke-juaxn--mori-qsakurag" + + "awaxn--mosjen-eyawaraxn--mot-tlavagiskexn--mre-og-romsdal-qqbuse" + + "rveexchangexn--msy-ula0haldenxn--mtta-vrjjat-k7aflakstadaokagaki" + + "cks-assnasaarlandxn--muost-0qaxn--mxtq1misawaxn--ngbc5azdxn--ngb" + + "e9e0axn--ngbrxn--3ds443gxn--nit225kosaigawaxn--nmesjevuemie-tcba" + + "lsan-suedtirolkuszczytnoipirangalsacexn--nnx388axn--nodessakurai" + + "s-very-evillagexn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--nts" + + "q17gxn--nttery-byaeservehttpixolinoxn--nvuotna-hwaxn--nyqy26axn-" + + "-o1acheltenham-radio-openairbusantiquest-a-la-maisondre-landroid" + + "xn--o3cw4halsaikitahiroshimaoris-a-knightpointtohobby-sitexn--o3" + + "cyx2axn--od0algxn--od0aq3bestbuyshouses3-website-ap-southeast-2x" + + "n--ogbpf8flekkefjordxn--oppegrd-ixaxn--ostery-fyawatahamaxn--osy" + + "ro-wuaxn--otu796dxn--p1acfermochizukirovogradoyxn--p1ais-very-go" + + "odyearxn--pbt977coguchikuzenxn--pgbs0dhlxn--porsgu-sta26ferrarax" + + "n--pssu33lxn--pssy2uxn--q9jyb4collectionxn--qcka1pmckinseyxn--qq" + + "qt11misconfusedxn--qxa6axn--qxamuneustargardxn--rady-iraxn--rdal" + + "-poaxn--rde-ulavangenxn--rdy-0nabaris-very-nicexn--rennesy-v1axn" + + "--rhkkervju-01aferraris-a-designerxn--rholt-mragowoodsidemoneyxn" + + "--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5nativeamericanan" + + "tiquestuff-4-salexn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rms" + + "kog-byaxn--rny31hammarfeastafricapetownnews-stagingxn--rovu88bet" + + "ainaboxfusejnyonagunicommbankaratsuginamikatagamilitaryoriikarel" + + "ianceu-2xn--rros-granvindafjordxn--rskog-uuaxn--rst-0naturalhist" + + "orymuseumcenterxn--rsta-francaiseharaxn--rvc1e0am3exn--ryken-vua" + + "xn--ryrvik-byaxn--s-1faithamurakamigoris-a-landscaperugiaxn--s9b" + + "rj9colognexus-2xn--sandnessjen-ogbhzcateringebuildingjesdalimite" + + "diskussionsbereichaseljeepsondriodejaneirockartuzyoshiokanzakiyo" + + "kawaraxn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sg" + + "ne-gratangenxn--skierv-utazastufftoread-booksnesolutionsokananii" + + "mihoboleslawiecitadeliveryggeexn--skjervy-v1axn--skjk-soaxn--skn" + + "it-yqaxn--sknland-fxaxn--slat-5naturalsciencesnaturellestuttgart" + + "revisohughesomaxn--slt-elabcieszynxn--smla-hraxn--smna-gratis-a-" + + "bulls-fanxn--snase-nraxn--sndre-land-0cbieigersundisrechtraining" + + "jovikariyaltakasugaincheonikonanporocpanamatsuzakindianapolis-a-" + + "anarchistoireggiocalabriaxn--snes-poaxn--snsa-roaxn--sr-aurdal-l" + + "8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbielawalmartjm" + + "axxxboxenapponazure-mobileu-3xn--srfold-byaxn--srreisa-q1axn--sr" + + "um-grazxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbiellaa" + + "kesvuemielecceu-4xn--stre-toten-zcbieszczadygeyachimataikikugawa" + + "ltervistaprinternationalfirearms3-website-eu-west-1xn--t60b56axn" + + "--tckweatherchannelxn--tiq49xqyjetztrentino-suedtirolxn--tjme-hr" + + "axn--tn0agrinetbankosakaerodromegallupinbarrel-of-knowledgestack" + + "arasjohkamikoaniikappuboliviajessheimetacentrumeteorappalmaserat" + + "in-the-bandain-vpncasinordkappalmspringsakerevistaples3-us-gov-w" + + "est-1xn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol" + + "-rzbievat-band-campaniaxn--trentin-sdtirol-7vbifukagawashingtond" + + "clkarlsoyukindianmarketingladefinimakanegasakiraxn--trentino-sd-" + + "tirol-c3bigv-infoodnetworkangerxn--trentino-sdtirol-szbihorology" + + "ukuhashimoichinosekigaharaxn--trentinosd-tirol-rzbikedaejeonbukl" + + "ugsmileborkdalvdalaheadjudygarlandivtasvuodnakaiwamizawatchandcl" + + "ockarmoyurihonjournalistjohninohekinannestadivttasvuotnakamagaya" + + "habahcavuotnagaivuotnagaokakyotambabydgoszczecinemagentositelema" + + "rkarpaczeladzjampagefrontappanasonicatholicaxiashorokanaievje-og" + + "-hornnes3-website-sa-east-1xn--trentinosdtirol-7vbilbaokinawashi" + + "rosatochigiessensiositecnologiaxn--trentinsd-tirol-6vbillustrati" + + "onredumbrellahppiacenzachpomorskieninomiyakonojorpelandiyusuhara" + + "xn--trentinsdtirol-nsbioddaxn--trgstad-r1axn--trna-woaxn--troms-" + + "zuaxn--tysvr-vraxn--uc0atvarggatritonxn--uc0ay4axn--uist22handso" + + "nyoursidellogliastradingxn--uisz3gxn--unjrga-rtarumizusawaxn--un" + + "up4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtunkomvuxn--2scrj9" + + "christmasakindlefrakkestadyndns-homednsanjotoyouraxn--valle-d-ao" + + "ste-ehbodollsusakis-into-gamessinazawaxn--valleaoste-e7axn--vall" + + "edaoste-ebbvacationsusonoxn--vard-jraxn--vegrshei-c0axn--vermgen" + + "sberater-ctbirdartcenterprisecloudcontrolapplebtimnetzlglassassi" + + "nationalheritagexn--vermgensberatung-pwbirkenesoddtangenovaranza" + + "nquanpachigasakievennodesabaerobatickets3-website-us-east-1xn--v" + + "estvgy-ixa6oxn--vg-yiabkhaziaxn--vgan-qoaxn--vgsy-qoa0jevnakersh" + + "uscultureggioemiliaromagnamsosnowiechristiansburgriwataraidyndns" + + "-freeboxosloftranakaniikawatanaguraxn--vgu402colonialwilliamsbur" + + "grondarxn--vhquvaroyxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadx" + + "n--vry-yla5gxn--vuq861birthplacexn--w4r85el8fhu5dnraxn--w4rs40lx" + + "n--wcvs22dxn--wgbh1coloradoplateaudioxn--wgbl6axn--xhq521bjarkoy" + + "usuisservehumourxn--xkc2al3hye2axn--xkc2dl3a5ee0hangglidingxn--y" + + "9a3aquariumishimasudaxn--yer-znaturbruksgymnxn--yfro4i67oxn--yga" + + "rden-p1axn--ygbi2ammxn--3e0b707exn--ystre-slidre-ujbjerkreimbamb" + + "lebesbyeniwaizumiotsukumiyamazonawsmpplanetariumemorialillyolasi" + + "tebizenakanojoetsuwanouchikujogaszkolancashirecreationavuotnaple" + + "s3-external-1xn--zbx025dxn--zf0ao64axn--zf0avxn--3hcrj9civilavia" + + "tionissedaluccapitalonewportlligatoystre-slidrettozawaxn--zfr164" + + "bjugnieznord-frontierxnbayxz" // nodes is the list of nodes. Each node is represented as a uint32, which // encodes the node's children, wildcard bit and node type (as an index into @@ -523,1816 +524,1805 @@ const text = "9guacuiababia-goracleaningroks-theatree12hpalermomahachijoinvill" // [15 bits] text index // [ 6 bits] text length var nodes = [...]uint32{ - 0x324003, - 0x3ac784, - 0x2e6d06, - 0x2f86c3, - 0x2f86c6, - 0x38e946, - 0x3b2f83, - 0x316b04, - 0x3280c7, - 0x2e6948, + 0x32ce03, + 0x243304, + 0x2d7946, + 0x215803, + 0x215806, + 0x38b3c6, + 0x3ae643, + 0x246d44, + 0x341047, + 0x2d7588, 0x1a000c2, - 0x1f3f4c7, - 0x378d49, - 0x2c3d8a, - 0x2c3d8b, - 0x234283, - 0x235a45, - 0x2203782, - 0x3c3f44, - 0x23be43, - 0x327905, - 0x260e2c2, - 0x3425c3, - 0x2a1f004, - 0x331f05, - 0x2e12502, - 0x270d0e, - 0x251a03, - 0x3a7ac6, - 0x3202e82, - 0x2cf107, - 0x238046, - 0x3602a42, - 0x223743, - 0x280904, - 0x2171c6, - 0x3381c8, - 0x289106, - 0x271dc4, + 0x1f3aec7, + 0x377a09, + 0x2c628a, + 0x2c628b, + 0x231b43, + 0x233805, + 0x2203042, + 0x212284, + 0x2d7ac3, + 0x203045, + 0x260c6c2, + 0x3290c3, + 0x2b22c44, + 0x33f285, + 0x2e0c182, + 0x26d6ce, + 0x24e5c3, + 0x3a36c6, + 0x3206082, + 0x2fd2c7, + 0x236086, + 0x3602982, + 0x27f103, + 0x27f104, + 0x397646, + 0x36bf08, + 0x288086, + 0x270104, 0x3a00ac2, - 0x34c289, - 0x215c87, - 0x331a86, - 0x2fb689, - 0x36dc08, - 0x34a384, - 0x36a106, - 0x248746, - 0x3e03082, - 0x22a54f, - 0x211b0e, - 0x216684, - 0x213985, - 0x323f05, - 0x2e3949, - 0x23ec89, - 0x2179c7, - 0x21e906, - 0x238a03, - 0x42197c2, - 0x2197c3, - 0x30d24a, - 0x4631643, - 0x255b85, - 0x2fae02, - 0x38f789, - 0x4e03002, - 0x204dc4, - 0x203006, - 0x32cec5, - 0x36f104, - 0x5624404, - 0x21c183, - 0x234dc4, - 0x5a04182, - 0x23afc4, - 0x5f8d7c4, - 0x37bc4a, + 0x34cb09, + 0x2171c7, + 0x344986, + 0x28dfc9, + 0x32fa48, + 0x34b444, + 0x3947c6, + 0x336a46, + 0x3e03582, + 0x3da686, + 0x24070f, + 0x2112ce, + 0x217bc4, + 0x20d005, + 0x32cd05, + 0x2e1b49, + 0x23b549, + 0x397e47, + 0x3cffc6, + 0x28e143, + 0x4212082, + 0x2232c3, + 0x28da0a, + 0x4613583, + 0x3cea45, + 0x299082, + 0x38c209, + 0x4e02282, + 0x213c04, + 0x21fb46, + 0x2fff45, + 0x36db84, + 0x5643344, + 0x225843, + 0x232b84, + 0x5a03342, + 0x31fd84, + 0x5f8a244, + 0x2fe64a, 0x6200882, - 0x3ba4c7, - 0x212208, - 0x7204602, - 0x3bda07, - 0x22f344, - 0x2c1247, - 0x22f345, - 0x361307, - 0x326546, - 0x28c784, - 0x342345, - 0x2774c7, - 0x8a02cc2, - 0x245a03, - 0x20bd02, - 0x36af43, - 0x8e09fc2, - 0x282e45, + 0x21ef47, + 0x27afc8, + 0x7204c82, + 0x2f6e47, + 0x2c2b84, + 0x2c2b87, + 0x3d6805, + 0x362187, + 0x2e73c6, + 0x27d8c4, + 0x328e45, + 0x256407, + 0x8a05802, + 0x3da803, + 0x21e182, + 0x369a03, + 0x8e09bc2, + 0x281705, 0x9200202, - 0x2452c4, - 0x3a9905, - 0x2165c7, - 0x366b8e, - 0x2b1b04, - 0x263a84, - 0x20fc03, - 0x268309, - 0x26a0cb, - 0x27e9c8, - 0x2fb448, - 0x353748, - 0x28dc48, - 0x34a1ca, - 0x361207, - 0x2c6606, - 0x9682702, - 0x375fc3, - 0x3cb183, - 0x3ccb84, - 0x376003, - 0x363c03, - 0x173b542, + 0x3c2844, + 0x277445, + 0x217b07, + 0x2fdfce, + 0x2b1044, + 0x261dc4, + 0x20e5c3, + 0x251789, + 0x265f0b, + 0x273788, + 0x28dd88, + 0x2e53c8, + 0x28c008, + 0x34b28a, + 0x362087, + 0x276586, + 0x9615842, + 0x2be403, + 0x3cab03, + 0x3cd244, + 0x2be443, + 0x28ca83, + 0x1736f42, 0x9a019c2, - 0x27fec5, - 0x339e46, - 0x235804, - 0x37ab87, - 0x23ae06, - 0x2ba3c4, - 0x394487, + 0x27e945, + 0x313dc6, + 0x2335c4, + 0x379907, + 0x263e46, + 0x2bfa04, + 0x399647, 0x2019c3, - 0x9ecab82, - 0xa21c342, - 0xa61c102, - 0x21c106, + 0x9ecb4c2, + 0xa227682, + 0xa627442, + 0x227446, 0xaa00282, - 0x284505, - 0x33ca83, - 0x3c2804, - 0x2e99c4, - 0x2e99c5, - 0x3c6083, - 0xae4af03, - 0xb33d3c2, - 0x28d145, - 0x3dc00b, - 0x3c65cb, - 0x226804, - 0x204389, - 0x205504, - 0xb605742, - 0x205f43, - 0x207583, - 0xba08d42, - 0x2ed10a, - 0xbe09002, - 0x3c41c5, - 0x2df2ca, - 0x390144, - 0x20b803, - 0x20c0c4, - 0x20d3c3, - 0x20d3c4, - 0x20d3c7, - 0x20e185, - 0x20ec06, - 0x20eec6, - 0x212fc3, - 0x216a08, - 0x20c483, - 0xc21c782, - 0x246648, - 0x38764b, - 0x21fe48, - 0x220c06, - 0x221187, - 0x224f08, - 0xd205142, - 0xd6bfa42, - 0x332048, - 0x210207, - 0x309b05, - 0x309b08, - 0xdac8208, - 0x2a9e43, - 0x22b9c4, - 0x38e9c2, - 0xde2bd82, - 0xe228b82, - 0xea2c542, - 0x22c543, - 0xee07982, - 0x305b43, - 0x238fc4, - 0x207983, - 0x31f8c4, - 0x332c0b, - 0x280c03, - 0x2e4a86, - 0x280c04, - 0x2b800e, - 0x384a45, - 0x3a7bc8, - 0x2fa347, - 0x2fa34a, - 0x223103, - 0x3ac587, - 0x26a285, - 0x231cc4, - 0x254146, - 0x254147, - 0x2f6e04, - 0x22ee47, - 0xf309544, - 0x37b904, - 0x37b906, - 0x2595c4, - 0x3a8c46, - 0x20bb03, - 0x3b9a08, - 0x20bb08, - 0x263a43, - 0x2ed0c3, - 0x3453c4, - 0x356ac3, - 0xfa47502, - 0xfe8d382, - 0x2056c3, - 0x243e86, - 0x342883, - 0x380c04, - 0x10216482, - 0x24ab83, - 0x216483, - 0x213e42, - 0x10600d42, - 0x2c4586, - 0x236707, - 0x3be887, - 0x3c6a85, - 0x39ad84, - 0x290e85, - 0x239b07, - 0x2dbc49, - 0x2e1b06, - 0x2e5d88, - 0x2fd486, - 0x10a27f02, - 0x3db188, - 0x2fe246, - 0x227f05, - 0x3184c7, - 0x318b04, - 0x318b05, - 0x10f27544, - 0x327548, - 0x11214c42, - 0x11600482, - 0x212746, - 0x200488, - 0x358ec5, - 0x359946, - 0x35b9c8, - 0x3804c8, - 0x11a0e745, - 0x11e22584, - 0x245c47, - 0x12204f82, - 0x126291c2, - 0x13a03102, - 0x203105, - 0x2864c5, - 0x386c06, - 0x34f647, - 0x22db47, - 0x1422f743, - 0x30f807, - 0x37af08, - 0x1e22f909, - 0x270ec7, - 0x22fd87, - 0x2309c8, - 0x2311c6, - 0x2317c6, - 0x2324cc, - 0x23394a, - 0x2342c7, - 0x23590b, - 0x236547, - 0x23654e, - 0x1e6373c4, - 0x2375c4, - 0x238e87, - 0x25e687, - 0x23e546, - 0x23e547, - 0x344b07, - 0x26f503, - 0x1ea2ca82, - 0x23f306, - 0x23f30a, - 0x23fd0b, - 0x241187, - 0x241c05, - 0x242143, - 0x242c46, - 0x242c47, - 0x2eaf03, - 0x1ee00102, - 0x2434ca, - 0x1f37a202, - 0x1f647542, - 0x1fa46342, - 0x1fe38142, - 0x246a85, - 0x247204, - 0x20624502, - 0x23b045, - 0x23b843, - 0x205605, - 0x202984, - 0x20adc4, - 0x3671c6, - 0x27f2c6, - 0x2a75c3, - 0x3c0544, - 0x3beb83, - 0x21607682, - 0x221504, - 0x2461c6, - 0x221505, - 0x249f46, - 0x3185c8, - 0x225304, - 0x22e988, - 0x337f85, - 0x33aa08, - 0x2bc7c6, - 0x359d87, - 0x2877c4, - 0x22a877c6, - 0x22e3c8c3, - 0x3a03c3, - 0x2e6648, - 0x329044, - 0x23360887, - 0x23add286, - 0x2dd289, - 0x32d3c8, - 0x268a48, - 0x329bc4, - 0x204c03, - 0x2403c2, - 0x23e50002, - 0x24201d42, - 0x207ec3, - 0x2460ce02, - 0x2eae84, - 0x372c86, - 0x328205, - 0x21b703, - 0x2b6f47, - 0x3301c3, - 0x336588, - 0x210a05, - 0x25b603, - 0x3a9885, - 0x3a99c4, - 0x3006c6, - 0x212a46, - 0x216506, - 0x203884, - 0x236903, - 0x24a08682, - 0x24f39905, - 0x200843, - 0x25612602, - 0x22f8c3, - 0x23ce85, - 0x25a34e83, - 0x26234e89, - 0x26600942, - 0x26e07bc2, - 0x28cc85, - 0x214b46, - 0x208a86, - 0x2ceb88, - 0x2ceb8b, - 0x3293cb, - 0x2fe6c5, - 0x2cb2c9, - 0x1600b42, - 0x2e7008, - 0x204684, - 0x27601bc2, - 0x34bf03, - 0x27e5e846, - 0x25d348, - 0x28201a02, - 0x28e248, - 0x28669102, - 0x340aca, - 0x28ad0703, - 0x29379386, - 0x37c3c8, - 0x30ad48, - 0x3c2d46, - 0x38acc7, - 0x22a747, - 0x2482ca, - 0x3901c4, - 0x35e684, - 0x3786c9, - 0x297ac185, - 0x211d06, - 0x210403, - 0x24fa44, - 0x29a227c4, - 0x331247, - 0x29eaabc7, - 0x2a1144, - 0x387c05, - 0x386cc8, - 0x3a47c7, - 0x247847, - 0x2a2143c2, - 0x3c51c4, - 0x294548, - 0x2494c4, - 0x24bd44, - 0x24c6c5, - 0x24c807, - 0x2a64e689, - 0x24e184, - 0x24ee09, - 0x24f048, - 0x24f7c4, - 0x24f7c7, - 0x2aa4fe03, - 0x250a47, - 0x16014c2, - 0x16b2642, - 0x251a46, - 0x251ec7, - 0x252304, - 0x253107, - 0x2547c7, - 0x255043, - 0x2b1d82, - 0x20d682, - 0x2fb543, - 0x3c0fc4, - 0x3c0fcb, - 0x2aefb548, - 0x25afc4, - 0x2572c5, - 0x258807, - 0x25a1c5, - 0x2def0a, - 0x25af03, - 0x2b208142, - 0x20c384, - 0x25e449, - 0x2628c3, - 0x262987, - 0x353b89, - 0x3d9dc8, - 0x229b43, - 0x27dbc7, - 0x27e549, - 0x239cc3, - 0x286b44, - 0x287dc9, - 0x28a706, - 0x339283, - 0x202ec2, - 0x237c43, - 0x2b2447, - 0x237c45, - 0x3bd6c6, - 0x2d7004, - 0x3cd145, - 0x27b483, - 0x213206, - 0x211603, - 0x204582, - 0x24aec4, - 0x2b6268c2, - 0x2ba268c3, - 0x2be020c2, - 0x24a403, - 0x20f344, - 0x20f347, - 0x390246, - 0x27cec2, - 0x2c25ec42, - 0x3187c4, - 0x2c60d542, - 0x2ca19202, - 0x23d144, - 0x23d145, - 0x28c405, - 0x366186, - 0x2ce0f4c2, - 0x32a305, - 0x354585, - 0x223383, - 0x3cf0c6, - 0x20f4c5, - 0x21c082, - 0x359585, - 0x21c084, - 0x225243, - 0x225483, - 0x2d209102, - 0x2dac07, - 0x24f244, - 0x24f249, - 0x24f944, - 0x286343, - 0x39f748, - 0x2d686344, - 0x286346, - 0x2a57c3, - 0x257b03, - 0x224943, - 0x2dae9882, - 0x2faf82, - 0x2de00642, - 0x33e548, - 0x342a88, - 0x3b3706, - 0x25adc5, - 0x22bc45, - 0x3306c7, - 0x2e26fbc5, - 0x203942, - 0x2e698402, - 0x2ea00042, - 0x315e88, - 0x3db0c5, - 0x2efec4, - 0x249e85, - 0x24b947, - 0x25f444, - 0x2433c2, - 0x2ee08b82, - 0x350584, - 0x217c07, - 0x28f207, - 0x3612c4, - 0x3cbb43, - 0x263984, - 0x263988, - 0x231b06, - 0x253fca, - 0x2aa144, - 0x2967c8, - 0x28ab44, - 0x221286, - 0x2983c4, - 0x203406, - 0x24f509, - 0x23c347, - 0x21fa83, - 0x2f25ce02, - 0x387f43, - 0x205942, - 0x2f616842, - 0x2f5d86, - 0x3836c8, - 0x2a74c7, - 0x225a09, - 0x2aba89, - 0x2a9385, - 0x2ab689, - 0x2ac745, - 0x2ad585, - 0x2ae048, - 0x2fa07a44, - 0x2fe55187, - 0x230143, - 0x2ae247, - 0x230146, - 0x2ae647, - 0x2a4ac5, - 0x2b9ac3, - 0x30233702, - 0x20ba44, - 0x3062cc02, - 0x30a05dc2, - 0x3cc0c6, - 0x212185, - 0x2b1047, - 0x326fc3, - 0x363b84, - 0x201e83, - 0x2c5e03, - 0x30e03842, - 0x31601cc2, - 0x38ea44, - 0x2dca43, - 0x2f6a45, - 0x31a00f42, - 0x32203702, - 0x2d8946, - 0x329184, - 0x3dacc4, - 0x3dacca, - 0x32a005c2, - 0x245e83, - 0x21368a, - 0x214348, - 0x32e21b84, - 0x2005c3, - 0x33201803, - 0x26c409, - 0x207349, - 0x2b7046, - 0x33614503, - 0x315405, - 0x3b478d, - 0x214506, - 0x21a4cb, - 0x33a03302, - 0x2b4148, - 0x36a16b02, - 0x36e08242, - 0x2e8205, - 0x37201742, - 0x371347, - 0x2022c3, - 0x20f688, - 0x376051c2, - 0x32e244, - 0x210743, - 0x32aa45, - 0x240406, - 0x21da04, - 0x2ed083, - 0x2b3003, - 0x37a150c2, - 0x2fe644, - 0x3bc6c5, - 0x2b2047, - 0x27b8c3, - 0x2b2a03, - 0x16b2ac2, - 0x2b2ac3, - 0x2b2f83, - 0x37e00e02, - 0x3bac04, - 0x27f4c6, - 0x23d303, - 0x2b36c3, - 0x3824b4c2, - 0x24b4c8, - 0x2b4e04, - 0x369ac6, - 0x256e47, - 0x284746, - 0x2aed84, - 0x46601b82, - 0x23000b, - 0x2f378e, - 0x2156cf, - 0x366703, - 0x46e5bd02, - 0x1638902, - 0x472017c2, - 0x296d43, - 0x2092c3, - 0x21d086, - 0x2dbec6, - 0x218d87, - 0x3cb504, - 0x47614c82, - 0x47a10f02, - 0x242b05, - 0x2f1d87, - 0x2b5dc6, - 0x47e47482, - 0x32a244, - 0x2bae03, - 0x48251b42, - 0x48774343, - 0x2bbe44, - 0x2c0549, - 0x48ac7542, - 0x48e01882, - 0x201885, - 0x492c8802, - 0x49604b42, - 0x35d987, - 0x3b6a09, - 0x378fcb, - 0x22a505, - 0x26aa09, - 0x270246, - 0x393bc7, - 0x49a04b44, - 0x3d6a49, - 0x37ed07, - 0x206a07, - 0x22c843, - 0x2b3fc6, - 0x322ec7, - 0x20b003, - 0x371f06, - 0x4a219842, - 0x4a612702, - 0x329a83, - 0x38f945, - 0x21f787, - 0x2dbfc6, - 0x237bc5, - 0x24f1c4, - 0x2a3fc5, - 0x390c84, - 0x4aa01b02, - 0x3be187, - 0x2c51c4, - 0x207244, - 0x20724d, - 0x34ca49, - 0x22cb48, - 0x201b04, - 0x31f985, - 0x20a247, - 0x20a5c4, - 0x266407, - 0x2aa705, - 0x4ae0e504, - 0x2b3145, - 0x261584, - 0x310fc6, - 0x35e105, - 0x4b224382, - 0x2126c3, - 0x3a2a83, - 0x346984, - 0x346985, - 0x361d86, - 0x237d05, - 0x229ac4, - 0x34dfc3, - 0x4b60cd46, - 0x223dc5, - 0x22ae85, - 0x34f544, - 0x2aa1c3, - 0x2aa1cc, - 0x4ba07dc2, - 0x4be01442, - 0x4c20e942, - 0x20e943, - 0x20e944, - 0x4c605542, - 0x353408, - 0x3bd785, - 0x2bd504, - 0x23e3c6, - 0x4ca065c2, - 0x4ce11e02, - 0x4d200c42, - 0x292905, - 0x203746, - 0x2aab04, - 0x217706, - 0x34b306, - 0x227803, - 0x4d737c4a, - 0x2747c5, - 0x30d203, - 0x222306, - 0x3ceec9, - 0x222307, - 0x297688, - 0x36dac9, - 0x326e08, - 0x222e46, - 0x20fbc3, - 0x4da030c2, - 0x3a17c8, - 0x4de49602, - 0x4e205982, - 0x225a83, - 0x2e1985, - 0x29b6c4, - 0x393d09, - 0x3a93c4, - 0x376588, - 0x4ea05983, - 0x4ef33084, - 0x214b88, - 0x207187, - 0x4f32a2c2, - 0x247702, - 0x323e85, - 0x264149, - 0x211d83, - 0x2814c4, - 0x3153c4, - 0x20a2c3, - 0x282a0a, - 0x4f727e02, - 0x4fa0b882, - 0x2cab03, - 0x3928c3, - 0x162f7c2, - 0x3757c3, - 0x4fe1f382, - 0x50200bc2, - 0x50746484, - 0x388586, - 0x26bc04, - 0x27aac3, - 0x3d4c43, - 0x50a00bc3, - 0x240086, - 0x3a5005, - 0x2cac87, - 0x2cdc45, - 0x2cee46, - 0x2d0048, - 0x2d0246, - 0x265cc4, - 0x29cc4b, - 0x2d41c3, - 0x2d41c5, - 0x2d4c48, - 0x203282, - 0x35dc82, - 0x50e46b02, - 0x51204fc2, - 0x214cc3, - 0x5166ff42, - 0x26ff43, - 0x2d5603, - 0x51e23c42, - 0x522da646, - 0x2578c6, - 0x526b4342, - 0x52a075c2, - 0x52e254c2, - 0x5323c9c2, - 0x53618382, - 0x53a02142, - 0x206183, - 0x2c9805, - 0x326406, - 0x53e09c04, - 0x245fca, - 0x3aa986, - 0x20d784, - 0x26a083, - 0x54a05682, - 0x208ac2, - 0x22f883, - 0x54e0ce83, - 0x3bd1c7, - 0x35e007, - 0x56e6f947, - 0x321a47, - 0x229483, - 0x22948a, - 0x265e44, - 0x3125c4, - 0x3125ca, - 0x22dc85, - 0x57211cc2, - 0x2522c3, - 0x57600602, - 0x22c483, - 0x387f03, - 0x57e00582, - 0x37ae84, - 0x3308c4, - 0x3ce845, - 0x319b45, - 0x28ea86, - 0x2aad46, - 0x5824dc02, - 0x58602f42, - 0x312a85, - 0x2575d2, - 0x351086, - 0x271283, - 0x301046, - 0x2ef145, - 0x1607f02, - 0x60a08d82, - 0x378043, - 0x208d83, - 0x282583, - 0x60e18782, - 0x23fc83, - 0x6160dbc2, - 0x2035c3, - 0x3bac48, - 0x26d843, - 0x26d846, - 0x32b747, - 0x31bb46, - 0x31bb4b, - 0x20d6c7, - 0x2e6444, - 0x61e00c02, - 0x3bd605, - 0x6220ce43, - 0x209b43, - 0x32c2c5, - 0x335703, - 0x62b35706, - 0x2e718a, - 0x2a3283, - 0x2170c4, - 0x2003c6, - 0x228306, - 0x62e3f7c3, - 0x363a47, - 0x26c307, - 0x29e485, - 0x281ec6, - 0x223e03, - 0x65bcf303, - 0x65e00a82, - 0x663406c4, - 0x3b9809, - 0x207f45, - 0x227484, - 0x3531c8, - 0x23d505, - 0x6666fcc5, - 0x242249, - 0x331b43, - 0x2474c4, - 0x66a04ac2, - 0x214ec3, - 0x66e7a042, - 0x27a046, - 0x167ce82, - 0x67203482, - 0x292808, - 0x2a2203, - 0x2b3087, - 0x31bdc5, - 0x2e2fc5, - 0x34e14b, - 0x2e2fc6, - 0x34e346, - 0x2e3f06, - 0x280104, - 0x2c0746, - 0x676d7a48, - 0x280cc3, - 0x266a03, - 0x266a04, - 0x2fb244, - 0x30a607, - 0x2e7b45, - 0x67ae7c82, - 0x67e07702, - 0x6861b5c5, - 0x2b5c44, - 0x2d8f4b, - 0x2e98c8, - 0x24de84, - 0x68a2c9c2, - 0x68e1a142, - 0x3a8bc3, - 0x2eba44, - 0x2ebd05, - 0x2ec547, - 0x2efa04, - 0x3613c4, - 0x69214e02, - 0x37e589, - 0x2f0b45, - 0x22a7c5, - 0x2f16c5, - 0x69614e03, - 0x2f2d04, - 0x2f2d0b, - 0x2f3044, - 0x2f330b, - 0x2f4345, - 0x21580a, - 0x2f4b08, - 0x2f4d0a, - 0x2f5543, - 0x2f554a, - 0x69e1c282, - 0x6a203542, - 0x6a628743, - 0x6aaf93c2, - 0x2f93c3, - 0x6af56bc2, - 0x6b33d202, - 0x2fab44, - 0x216b46, - 0x217445, - 0x2fe1c3, - 0x3245c6, - 0x216f45, - 0x22cec4, - 0x6b600902, - 0x2a7b04, - 0x2caf4a, - 0x32d147, - 0x335a86, - 0x3ac3c7, - 0x23f343, - 0x2bbe88, - 0x3bdbcb, - 0x2b7145, - 0x2c0d85, - 0x2c0d86, - 0x26cf04, - 0x3c0688, - 0x21be43, - 0x248644, - 0x248647, - 0x2e6086, - 0x203a86, - 0x2b7e4a, - 0x24ee84, - 0x314eca, - 0x6bb48d46, - 0x348d47, - 0x257347, - 0x276e44, - 0x276e49, - 0x233045, - 0x326a0b, - 0x2e8f43, - 0x212c03, - 0x6be1d5c3, - 0x2c9a04, - 0x6c200682, - 0x224746, - 0x6c6b9845, - 0x301285, - 0x251c86, - 0x2a0304, - 0x6ca03e42, - 0x242184, - 0x6ce129c2, - 0x228ac5, - 0x268804, - 0x6da1bbc3, - 0x6de08dc2, - 0x208dc3, - 0x238446, - 0x6e205fc2, - 0x395a48, - 0x222184, - 0x222186, - 0x393146, - 0x6e6588c4, - 0x20ccc5, - 0x222488, - 0x2250c7, - 0x227547, - 0x22754f, - 0x294446, - 0x231603, - 0x23ee84, - 0x20c5c3, - 0x2213c4, - 0x251dc4, - 0x6ea0ba82, - 0x28d083, - 0x337243, - 0x6ee02442, - 0x223943, - 0x21c2c3, - 0x20e20a, - 0x275b47, - 0x25494c, - 0x6f254c06, - 0x254d86, - 0x256b47, - 0x6f630e07, - 0x25d989, - 0x246784, - 0x262b04, - 0x6fa14d02, - 0x6fe05c82, - 0x2b8206, - 0x363844, - 0x28d506, - 0x231288, - 0x38fa04, - 0x259c46, - 0x208a45, - 0x7028b748, - 0x242d43, - 0x30d485, - 0x2917c3, + 0x285845, + 0x338483, + 0x3bfc44, + 0x2eddc4, + 0x2eddc5, + 0x3c7543, + 0xae48343, + 0xb338dc2, + 0x203c05, + 0x203c0b, + 0x20b24b, + 0x26bb44, + 0x204a09, + 0x205f44, + 0xb606802, + 0x207043, + 0x207183, + 0xba08082, + 0x2ed8ca, + 0xbe08342, + 0x212505, + 0x2de9ca, + 0x35cc04, + 0x208343, + 0x209ec4, + 0x20ba03, + 0x20ba04, + 0x20ba07, + 0x20c585, + 0x20d346, + 0x213006, + 0x213cc3, + 0x217f48, + 0x20db43, + 0xc209582, + 0x23d4c8, + 0x20958b, + 0x221cc8, + 0x222a86, + 0x224447, + 0x226fc8, + 0xd205b82, + 0xd6c1142, + 0x33f3c8, + 0x20f9c7, + 0x30f645, + 0x30f648, + 0xdadcf48, + 0x27ff43, + 0x22a104, + 0x38b442, + 0xde2a542, + 0xe243bc2, + 0xea2a8c2, 0x22a8c3, - 0x22a8c4, - 0x20c343, - 0x7064b902, - 0x70a033c2, - 0x2e8e09, - 0x292705, - 0x292a04, - 0x293245, - 0x20b444, - 0x3a2007, - 0x35fbc5, - 0x70e3bb84, - 0x2df6c8, - 0x2e0cc6, - 0x2e2e04, - 0x2e3508, - 0x2e3807, - 0x71201702, - 0x2e6804, - 0x30a484, - 0x2c1447, - 0x71605184, - 0x226b42, - 0x71a01782, + 0xee04042, + 0x30e303, + 0x237484, + 0x204043, + 0x206444, + 0x37454b, + 0x2094c3, + 0x2e94c6, + 0x27f404, + 0x2ba20e, + 0x381e45, + 0x3a37c8, + 0x3dd347, + 0x3dd34a, + 0x22f603, + 0x243107, + 0x2660c5, + 0x22f604, + 0x250206, + 0x250207, + 0x2fc304, + 0xf30f084, + 0x2fe304, + 0x2fe306, + 0x3db9c4, + 0x3ba486, + 0x226e03, + 0x3a8908, + 0x3c38c8, + 0x291d83, + 0x2ed883, + 0x346ac4, + 0x358183, + 0xfa09382, + 0xfe8b742, + 0x20b983, + 0x240d86, + 0x329383, + 0x35b7c4, + 0x102179c2, + 0x24a583, + 0x2179c3, + 0x214c82, + 0x10600d42, + 0x2c5c86, + 0x2344c7, + 0x2f8407, + 0x3a9405, + 0x207484, + 0x29bac5, + 0x267347, + 0x3cb009, + 0x2db306, + 0x2ea9c6, + 0x10a02c82, + 0x331448, + 0x31f486, + 0x34ebc5, + 0x3ac387, + 0x306104, + 0x306105, + 0x10e02c84, + 0x202c88, + 0x11203cc2, + 0x11600482, + 0x21d746, + 0x200488, + 0x335f85, + 0x34d406, + 0x351b08, + 0x35b088, + 0x11a07d45, + 0x11e25744, + 0x322d47, + 0x122059c2, + 0x1268cac2, + 0x13a0c302, + 0x21fc45, + 0x284a05, + 0x384006, + 0x326507, + 0x3a80c7, + 0x1422c0c3, + 0x318887, + 0x3a4548, + 0x1f22c289, + 0x26d887, + 0x22c9c7, + 0x22d408, + 0x22dc06, + 0x22f106, + 0x23000c, + 0x230d0a, + 0x231b87, + 0x2336cb, + 0x234307, + 0x23430e, + 0x1f635404, + 0x235604, + 0x237347, + 0x25bfc7, + 0x23ac06, + 0x23ac07, + 0x333e47, + 0x2e4003, + 0x1fa2ae02, + 0x23bec6, + 0x23beca, + 0x23c90b, + 0x23e487, + 0x23ef05, + 0x23f443, + 0x23f946, + 0x23f947, + 0x2ef083, + 0x1fe00102, + 0x24038a, + 0x20378f82, + 0x20661482, + 0x20a3d1c2, + 0x20e36182, + 0x242505, + 0x242cc4, + 0x21659dc2, + 0x31fe05, + 0x23cf03, + 0x2954c5, + 0x2028c4, + 0x20a204, + 0x280186, + 0x27e0c6, + 0x203e03, + 0x3bcfc4, + 0x2f8703, + 0x226081c2, + 0x2247c4, + 0x3232c6, + 0x2247c5, + 0x244086, + 0x3ac488, + 0x22b144, + 0x36b888, + 0x322805, + 0x37f488, + 0x2c24c6, + 0x3049c7, + 0x287804, + 0x23a87806, + 0x23ee85c3, + 0x39c943, + 0x2ec108, + 0x331344, + 0x24361707, + 0x24abe846, + 0x2dbb09, + 0x330048, + 0x34b8c8, + 0x355644, + 0x3c6d83, + 0x23cfc2, + 0x24e4cfc2, + 0x25201d42, + 0x204583, + 0x2560a782, + 0x2ef004, + 0x24ad06, + 0x21af83, + 0x2b6387, + 0x2f7443, + 0x334888, + 0x2101c5, + 0x259203, + 0x2773c5, + 0x277504, + 0x305e06, + 0x2127c6, + 0x217a46, + 0x2203c4, + 0x2346c3, + 0x25a05202, + 0x25e2ec05, + 0x200843, + 0x2660f4c2, + 0x22c243, + 0x373605, + 0x26a32c43, + 0x27232c49, + 0x27600942, + 0x27e04282, + 0x28b045, + 0x215e46, + 0x205606, + 0x2cf508, + 0x2cf50b, + 0x32ed0b, + 0x3a9605, + 0x2cbc09, + 0x1600b42, + 0x2cfc08, + 0x204d04, + 0x28601bc2, + 0x34a603, + 0x28e5c186, + 0x33e208, + 0x29201a02, + 0x28c608, + 0x29609802, + 0x33c4ca, + 0x29a46e03, + 0x2a378046, + 0x3910c8, + 0x330906, + 0x387087, + 0x240907, + 0x3365ca, + 0x35cc84, + 0x35fe04, + 0x376889, + 0x2a7a7545, + 0x2114c6, + 0x20fbc3, + 0x24bd04, + 0x2aa0d644, + 0x344147, + 0x2aee3587, + 0x293104, + 0x236cc5, + 0x3840c8, + 0x3a03c7, + 0x243547, + 0x2b20c202, + 0x298d44, + 0x294348, + 0x2443c4, + 0x249204, + 0x249b85, + 0x249cc7, + 0x2b658549, + 0x24a804, + 0x24b0c9, + 0x24b308, + 0x24ba84, + 0x24ba87, + 0x2ba4cdc3, + 0x24d2c7, + 0x2be014c2, + 0x16b1b82, + 0x24df86, + 0x24e607, + 0x24e884, + 0x24f687, + 0x250647, + 0x2510c3, + 0x2b12c2, + 0x20bcc2, + 0x28de83, + 0x3be3c4, + 0x3be3cb, + 0x2c28de88, + 0x258bc4, + 0x254205, + 0x255c47, + 0x238a05, + 0x2d908a, + 0x258b03, + 0x2c603d42, + 0x20da44, + 0x25bd89, + 0x2601c3, + 0x260287, + 0x2683c9, + 0x3de348, + 0x23e2c3, + 0x27c387, + 0x27ce49, + 0x266883, + 0x284fc4, + 0x286209, + 0x289406, + 0x2c7d43, + 0x2076c2, + 0x235c83, + 0x2b1987, + 0x235c85, + 0x3b8806, + 0x26e144, + 0x3cc645, + 0x279803, + 0x213f06, + 0x210dc3, + 0x204c02, + 0x248304, + 0x2ca6bc02, + 0x2ce6bc03, + 0x2d2020c2, + 0x247603, + 0x213484, + 0x239bc7, + 0x216586, + 0x278042, + 0x2d65c582, + 0x3ac684, + 0x2da0bb82, + 0x2de063c2, + 0x2b36c4, + 0x2b36c5, + 0x27d545, + 0x366a06, + 0x2e204882, + 0x3bd645, + 0x3cedc5, + 0x204883, + 0x21a286, + 0x21b845, + 0x2273c2, + 0x35acc5, + 0x2273c4, + 0x22b083, + 0x22b2c3, + 0x2e61d302, + 0x256607, + 0x24b504, + 0x24b509, + 0x24bc04, + 0x284883, + 0x39bf88, + 0x2ea84884, + 0x284886, + 0x2a6b43, + 0x254c43, + 0x228b03, + 0x2eeedc82, + 0x302342, + 0x2f200642, + 0x339f48, + 0x301408, + 0x3aedc6, + 0x272945, + 0x2802c5, + 0x345387, + 0x2f677f05, + 0x220482, + 0x2fa97642, + 0x2fe00042, + 0x278cc8, + 0x31f3c5, + 0x2f3e44, + 0x243fc5, + 0x245547, + 0x27a1c4, + 0x240282, + 0x30205702, + 0x352784, + 0x222f47, + 0x28cf47, + 0x362144, + 0x3cc143, + 0x291cc4, + 0x291cc8, + 0x22f446, + 0x25008a, + 0x2eb544, + 0x296008, + 0x242ec4, + 0x224546, + 0x297604, + 0x21ff46, + 0x24b7c9, + 0x2a62c7, + 0x2087c3, + 0x306033c2, + 0x34b643, + 0x206a02, + 0x30a17d82, + 0x2fb0c6, + 0x380708, + 0x2a8787, + 0x26ad49, + 0x2ad689, + 0x2aa9c5, + 0x2abd49, + 0x2ac545, + 0x2ad385, + 0x2ae008, + 0x30e04104, + 0x31251207, + 0x22cd83, + 0x2ae207, + 0x22cd86, + 0x2ae607, + 0x2a5e45, + 0x22c603, + 0x31630ac2, + 0x208584, + 0x31a0adc2, + 0x31e04742, + 0x3ae186, + 0x27af45, + 0x2b0587, + 0x2fef43, + 0x28ca04, + 0x201e83, + 0x20f703, + 0x32203dc2, + 0x32a01cc2, + 0x38b4c4, + 0x3881c3, + 0x2fbf45, + 0x32e00f42, + 0x33602b82, + 0x2d5c86, + 0x2fdf04, + 0x303f04, + 0x303f0a, + 0x33e005c2, + 0x263f43, + 0x20cd0a, + 0x214388, + 0x34224e44, + 0x2005c3, + 0x34601803, + 0x266ac9, + 0x24d8c9, + 0x2b6486, + 0x34a14543, + 0x36f705, + 0x3b62cd, + 0x214546, + 0x219e4b, + 0x34e129c2, + 0x394608, + 0x38218042, + 0x38604dc2, + 0x2b3905, + 0x38a01742, + 0x2c67c7, + 0x214903, + 0x21ba08, + 0x38e02cc2, + 0x219384, + 0x20ff03, + 0x2f7ac5, + 0x23d006, + 0x21e244, + 0x2ed843, + 0x2b26c3, + 0x392163c2, + 0x3a9584, + 0x3b77c5, + 0x2b1587, + 0x279c43, + 0x2b2183, + 0x16b2242, + 0x2b2243, + 0x2b2643, + 0x39600e02, + 0x246b84, + 0x27e2c6, + 0x3cba43, + 0x2b2d43, + 0x39a48902, + 0x248908, + 0x2b3d84, + 0x20ed06, + 0x255087, + 0x270906, + 0x291e84, + 0x47e01b82, + 0x22cc4b, + 0x2f91ce, + 0x216c0f, + 0x292e43, + 0x48659902, + 0x163ea82, + 0x48a017c2, + 0x296583, + 0x20e883, + 0x2dd4c6, + 0x3cb286, + 0x2b0187, + 0x30b0c4, + 0x48e11902, + 0x492106c2, + 0x245005, + 0x2f1887, + 0x2b47c6, + 0x496526c2, + 0x2526c4, + 0x2b93c3, + 0x49a4e082, + 0x49f72383, + 0x2bac04, + 0x2c1d89, + 0x4a2c8c82, + 0x4a601882, + 0x201885, + 0x4aac9182, + 0x4ae03c42, + 0x35f107, + 0x377c8b, + 0x2406c5, + 0x2570c9, + 0x268746, + 0x4b207844, + 0x328949, + 0x2c9cc7, + 0x32a547, + 0x22abc3, + 0x2b3546, + 0x3246c7, + 0x20a443, + 0x291246, + 0x4ba23342, + 0x4be1d702, + 0x34b783, + 0x38c3c5, + 0x221587, + 0x3cb386, + 0x235c05, + 0x24b484, + 0x2a4d05, + 0x38cf44, + 0x4c201b02, + 0x2c71c4, + 0x267f44, + 0x38830d, + 0x37adc9, + 0x22aec8, + 0x201b04, + 0x3dad85, + 0x3a8e87, + 0x206504, + 0x263f07, + 0x2eb205, + 0x4c607b04, + 0x2a8b45, + 0x25ee84, + 0x27a306, + 0x35f885, + 0x4ca26902, + 0x21d6c3, + 0x28f783, + 0x348084, + 0x348085, + 0x37c586, + 0x235d45, + 0x3d3284, + 0x32c043, + 0x4ce0a6c6, + 0x225045, + 0x225c85, + 0x326404, + 0x2eb5c3, + 0x2eb5cc, + 0x4d204482, + 0x4d601442, + 0x4da03102, + 0x20e403, + 0x20e404, + 0x4de05f82, + 0x380d88, + 0x3b88c5, + 0x2c93c4, + 0x23aa86, + 0x4e217002, + 0x4e6115c2, + 0x4ea00c42, + 0x291a85, + 0x220286, + 0x20d584, + 0x397b86, + 0x21ed06, + 0x221983, + 0x4ee9e10a, + 0x279e05, + 0x28d9c3, + 0x2254c6, + 0x3bd449, + 0x2254c7, + 0x2a9c48, + 0x32f909, + 0x3b9e48, + 0x303706, + 0x20e583, + 0x4f21fc02, + 0x39dc88, + 0x4f644502, + 0x4fa06a42, + 0x238cc3, + 0x2e2a45, + 0x29b404, + 0x2f5d89, + 0x32acc4, + 0x3dabc8, + 0x50206a43, + 0x507749c4, + 0x215e88, + 0x388247, + 0x50a52742, + 0x22e302, + 0x32cc85, + 0x261b89, + 0x211543, + 0x27fcc4, + 0x36f6c4, + 0x20e903, + 0x2812ca, + 0x50f40d82, + 0x512083c2, + 0x2cb443, + 0x38f5c3, + 0x162c142, + 0x2bdc03, + 0x5161d902, + 0x51a00bc2, + 0x51f03f84, + 0x3b3506, + 0x269884, + 0x278b03, + 0x3bf203, + 0x52200bc3, + 0x23cc86, + 0x3a0e45, + 0x2cb5c7, + 0x2cf7c6, + 0x2d0648, + 0x2d0846, + 0x2035c4, + 0x29cd0b, + 0x2d3643, + 0x2d3645, + 0x21fdc2, + 0x35f402, + 0x52642582, + 0x52a05a02, + 0x215fc3, + 0x52e6bf42, + 0x26bf43, + 0x2d46c3, + 0x5360cac2, + 0x53ad9bc6, + 0x257906, + 0x53ed9d02, + 0x542071c2, + 0x5462b302, + 0x54a09082, + 0x54e18942, + 0x552050c2, + 0x208b03, + 0x26cb45, + 0x379b06, + 0x55617b84, + 0x3230ca, + 0x3a5d46, + 0x20bdc4, + 0x28dd43, + 0x56212b02, + 0x205642, + 0x22c203, + 0x5660a803, + 0x3b8307, + 0x35f787, + 0x58ae4447, + 0x39e847, + 0x229183, + 0x333b4a, + 0x340644, + 0x319084, + 0x31908a, + 0x3a8205, + 0x58e11482, + 0x24df43, + 0x59200602, + 0x24bbc3, + 0x34b603, + 0x59a00582, + 0x3a44c4, + 0x345584, + 0x3b0645, + 0x31e4c5, + 0x2e4a06, + 0x304146, + 0x59e39242, + 0x5a202f42, + 0x33d185, + 0x257612, + 0x353286, + 0x270e03, + 0x356606, + 0x31cd05, + 0x16045c2, + 0x626080c2, + 0x376203, + 0x2080c3, + 0x396203, + 0x62a18d42, + 0x23a183, + 0x63223242, + 0x220103, + 0x300808, + 0x239503, + 0x239506, + 0x3c4d87, + 0x321186, + 0x32118b, + 0x20bd07, + 0x2ebf04, + 0x63a00c02, + 0x3b8745, + 0x63e09783, + 0x21d283, + 0x2e60c5, + 0x333a43, + 0x64733a46, + 0x3c8fca, + 0x2a3fc3, + 0x235f44, + 0x2003c6, + 0x34efc6, + 0x64a16603, + 0x340007, + 0x2669c7, + 0x29e985, + 0x26f486, + 0x2158c3, + 0x6761a4c3, + 0x67a00a82, + 0x67e8f804, + 0x3c36c9, + 0x2137c5, + 0x229bc4, + 0x354e88, + 0x2e47c5, + 0x682352c5, + 0x23f549, + 0x344a43, + 0x261404, + 0x686161c2, + 0x2161c3, + 0x68a74542, + 0x274546, + 0x1678002, + 0x68e08f82, + 0x291988, + 0x291c83, + 0x2a8a87, + 0x2b2745, + 0x2b22c5, + 0x2b22cb, + 0x2e8206, + 0x2b24c6, + 0x23bb44, + 0x2e8946, + 0x69321408, + 0x27f4c3, + 0x264503, + 0x264504, + 0x2e51c4, + 0x2ea707, + 0x2ec545, + 0x696ec682, + 0x69a08242, + 0x6a21ae45, + 0x2b8f44, + 0x2d244b, + 0x2edcc8, + 0x250f44, + 0x6a62ad42, + 0x6aa23c42, + 0x3ba403, + 0x2efb84, + 0x2efe45, + 0x2f0607, + 0x2f3984, + 0x362244, + 0x6ae16102, + 0x37b5c9, + 0x2f4c05, + 0x240985, + 0x2f5cc5, + 0x6b216103, + 0x2f67c4, + 0x2f67cb, + 0x2f8a84, + 0x2f8d4b, + 0x2f95c5, + 0x216d4a, + 0x2f9e88, + 0x2fa08a, + 0x2fa883, + 0x2fa88a, + 0x6ba13602, + 0x6be20082, + 0x6c2ba0c3, + 0x6c6fdb02, + 0x2fdb03, + 0x6caed182, + 0x6cf38c02, + 0x301f04, + 0x218086, + 0x3978c5, + 0x303c03, + 0x32d3c6, + 0x3973c5, + 0x3d2dc4, + 0x6d200902, + 0x29fc84, + 0x2cb88a, + 0x3001c7, + 0x32a006, + 0x242f47, + 0x23bf03, + 0x2bac48, + 0x3c608b, + 0x2b6585, + 0x2c26c5, + 0x2c26c6, + 0x229984, + 0x3a4f48, + 0x20f883, + 0x25b984, + 0x336947, + 0x2ebb46, + 0x340846, + 0x2ba04a, + 0x24b144, + 0x31b14a, + 0x6d7009c6, + 0x3009c7, + 0x254287, + 0x273f04, + 0x273f09, + 0x251e05, + 0x234f8b, + 0x2ed083, + 0x212983, + 0x6da1de03, + 0x331d84, + 0x6de00682, + 0x228906, + 0x6e2bb4c5, + 0x356845, + 0x24e1c6, + 0x2a1384, + 0x6e602442, + 0x23f484, + 0x6ea0a982, + 0x3287c5, + 0x34c884, + 0x6f61b443, + 0x6fa08102, + 0x208103, + 0x3062c6, + 0x6fe04e82, + 0x392248, + 0x225344, + 0x225346, + 0x38fe46, + 0x70255d04, + 0x20a645, + 0x225648, + 0x227187, + 0x34e087, + 0x34e08f, + 0x294246, + 0x23b743, + 0x23fac4, + 0x20dc83, + 0x224684, + 0x24e784, + 0x706085c2, + 0x28b443, + 0x335543, + 0x70a09482, + 0x209483, + 0x227603, + 0x20c60a, + 0x272bc7, + 0x25398c, + 0x70e53c46, + 0x253dc6, + 0x254d87, + 0x7122d847, + 0x25aac9, + 0x23d604, + 0x71660404, + 0x71a16002, + 0x71e02e42, + 0x2ba406, + 0x33fe04, + 0x28b8c6, + 0x22dcc8, + 0x38c484, + 0x2d7b46, + 0x2055c5, + 0x7228a748, + 0x23fa43, + 0x314705, + 0x28dc43, + 0x240a83, + 0x240a84, + 0x20da03, + 0x72648d42, + 0x72a03382, + 0x2ecf49, + 0x291b85, + 0x292544, + 0x296b45, + 0x209b04, + 0x2cd147, + 0x35a545, + 0x72e46484, + 0x2d2088, + 0x2d2f86, + 0x2dedc4, + 0x2e13c8, + 0x2e1a07, + 0x73201702, + 0x2e99c4, + 0x310d44, + 0x2c2d87, + 0x73605bc4, + 0x215782, + 0x73a01782, 0x201783, 0x201784, - 0x29f203, - 0x29f205, - 0x71e1df42, - 0x2fae85, - 0x287542, - 0x303a85, - 0x3b9145, - 0x72210b82, - 0x216404, - 0x726026c2, - 0x36b586, - 0x2ba0c6, - 0x264288, - 0x2c20c8, - 0x3cc044, - 0x3b2ac5, - 0x383b49, - 0x2fe744, - 0x2e7144, - 0x203983, - 0x248403, - 0x72a48405, - 0x234545, - 0x2865c4, - 0x35550d, - 0x354282, - 0x357a43, - 0x35ef43, - 0x72e05c42, - 0x73202302, - 0x395505, - 0x368687, - 0x21dc44, - 0x36dcc9, - 0x2cb089, - 0x279e83, - 0x279e88, - 0x2f3f49, - 0x2305c7, - 0x207ac5, - 0x383886, - 0x3a2c06, - 0x3a6305, - 0x34cb45, - 0x73601a82, - 0x27cbc5, - 0x2b8908, - 0x2c4346, - 0x73b2ec87, - 0x2a1084, - 0x309247, - 0x2ffe86, - 0x73e01082, - 0x361a86, - 0x30310a, - 0x303985, - 0x742e4542, - 0x7468f3c2, - 0x340fc6, - 0x318e08, - 0x74a8f3c7, - 0x74e28cc2, - 0x28a1c3, - 0x3a8546, - 0x224304, - 0x278b06, - 0x313646, - 0x376d4a, - 0x203bc5, - 0x331cc6, - 0x32ee03, - 0x32ee04, - 0x20b102, - 0x329103, - 0x7520e982, - 0x2ed803, - 0x213904, - 0x2c28c4, - 0x75718f4a, - 0x222f03, - 0x222f0a, - 0x239447, - 0x307186, - 0x23bd84, - 0x20d642, - 0x2a5b82, - 0x75a007c2, - 0x263943, - 0x257107, + 0x29f703, + 0x2aed05, + 0x73e2e942, + 0x302245, + 0x287582, + 0x30a205, + 0x3c0085, + 0x74210342, + 0x217944, + 0x74602602, + 0x28eb86, + 0x2bf706, + 0x261cc8, + 0x2c3748, + 0x3ae104, + 0x30ed05, + 0x3abbc9, + 0x2cfd44, + 0x3c8f84, + 0x2204c3, + 0x319c83, + 0x74b19c85, + 0x2411c5, + 0x284b04, + 0x356bcd, + 0x293042, + 0x359103, + 0x74e09442, + 0x75203a42, + 0x391d05, + 0x3babc7, + 0x21e484, + 0x32fb09, + 0x2cb9c9, + 0x277e43, + 0x277e48, + 0x245e09, + 0x214947, + 0x204185, + 0x37c106, + 0x37ec86, + 0x3808c5, + 0x37aec5, + 0x75601a82, + 0x287205, + 0x2b7748, + 0x2c5a46, + 0x75a52b07, + 0x2bd2c4, + 0x2fc647, + 0x305546, + 0x75e01082, + 0x37c286, + 0x30988a, + 0x30a105, + 0x762e8f82, + 0x76621902, + 0x3645c6, + 0x221908, + 0x76a8d107, + 0x76e43b02, + 0x288ec3, + 0x2ff806, + 0x226884, + 0x275b06, + 0x319f46, + 0x2034ca, + 0x2021c5, + 0x3006c6, + 0x2520c3, + 0x2520c4, + 0x207442, + 0x331403, + 0x7720e442, + 0x2f1803, + 0x7760cf84, + 0x221a44, + 0x77a21a4a, + 0x22ce03, + 0x266c87, + 0x30d106, + 0x2ff144, + 0x20bc82, + 0x2a6f02, + 0x77e007c2, + 0x22b9c3, + 0x254047, 0x2007c7, - 0x288e44, - 0x21f187, - 0x2ec646, - 0x210347, - 0x21c204, - 0x3ae645, - 0x20fa45, - 0x75e14642, - 0x387fc6, - 0x214643, - 0x21d882, - 0x21d886, - 0x76207202, - 0x7661a602, - 0x3c5285, - 0x76a44d02, - 0x76e01942, - 0x323485, - 0x2d4505, - 0x2a8485, - 0x77609903, - 0x372d45, - 0x2e3087, - 0x2f7405, - 0x203d85, - 0x322804, - 0x23d386, - 0x2fdc84, - 0x77a008c2, - 0x786e7645, - 0x3858c7, - 0x3543c8, - 0x250e06, - 0x38f0cd, - 0x250e09, - 0x250e12, - 0x349b05, - 0x34c683, - 0x78a05842, - 0x313244, - 0x214583, - 0x382b45, - 0x304945, - 0x78e10782, - 0x25b643, - 0x7921ddc2, - 0x79a736c2, - 0x79e00082, - 0x2d1885, - 0x21fb83, - 0x248d88, - 0x7a210242, - 0x7a613602, - 0x37ae46, - 0x31650a, - 0x206303, - 0x258dc3, - 0x2f1cc3, - 0x7ba04cc2, - 0x89e187c2, - 0x8a608c02, - 0x2038c2, - 0x3ccc09, - 0x2c6984, - 0x20a988, - 0x8aaf6602, - 0x8b205d42, - 0x2b04c5, - 0x235d48, - 0x2f9f08, - 0x2e95cc, - 0x239383, - 0x8b61fb02, - 0x8ba00f02, - 0x367b06, - 0x308005, - 0x2db783, - 0x250346, - 0x308146, - 0x291103, - 0x30a3c3, - 0x30a7c6, - 0x30c044, - 0x26c6c6, - 0x3b45ca, - 0x2443c4, - 0x30c704, - 0x30d84a, - 0x8bee5d02, - 0x24ff85, - 0x30f38a, - 0x30f2c5, - 0x3115c4, - 0x3116c6, - 0x311844, - 0x215186, - 0x8c22c902, - 0x2f8346, - 0x32b1c5, - 0x329847, - 0x3b4446, - 0x256d44, - 0x2dba07, - 0x337b86, - 0x20b085, - 0x20b087, - 0x3bc047, - 0x3bc04e, - 0x38cb86, - 0x221045, - 0x2050c7, - 0x207603, - 0x349047, - 0x208f45, - 0x217ec4, - 0x222782, - 0x22bdc7, - 0x3cb584, - 0x2ff104, - 0x24758b, - 0x21c903, - 0x291847, - 0x21c904, - 0x2ba647, - 0x22ac83, - 0x34e84d, - 0x3a4e08, - 0x8c6261c4, - 0x23ba85, - 0x312bc5, - 0x313003, - 0x8ca22082, - 0x315303, - 0x315703, - 0x388144, - 0x27e645, - 0x2146c7, - 0x32ee86, - 0x390b03, - 0x22550b, - 0x27e74b, - 0x2b2ccb, - 0x2d340b, - 0x2e458a, - 0x3709cb, + 0x287dc4, + 0x3d0847, + 0x2f0706, + 0x20fb07, + 0x227544, + 0x292445, + 0x2187c5, + 0x78214682, + 0x3b2f46, + 0x215943, + 0x21e0c2, + 0x21e0c6, + 0x78621542, + 0x78a19f82, + 0x298e05, + 0x78e47c82, + 0x79201942, + 0x324c85, + 0x2d3985, + 0x2a9385, + 0x79a1d043, + 0x24adc5, + 0x2e82c7, + 0x2f3505, + 0x202385, + 0x32b944, + 0x229a46, + 0x3dd844, + 0x79e008c2, + 0x7ab82d05, + 0x3c9447, + 0x3afe08, + 0x24d686, + 0x38bb4d, + 0x24d689, + 0x24d692, + 0x34cf05, + 0x37aa03, + 0x7ae06902, + 0x319b44, + 0x2145c3, + 0x38d005, + 0x30b405, + 0x7b20ff42, + 0x259243, + 0x7b62b902, + 0x7beca302, + 0x7c200082, + 0x2e08c5, + 0x2088c3, + 0x7c60fa02, + 0x7ca14302, + 0x3a4486, + 0x27ac8a, + 0x208c83, + 0x256203, + 0x2f6ac3, + 0x7de05402, + 0x8c218d82, + 0x8ca05782, + 0x217042, + 0x3cd2c9, + 0x2c80c4, + 0x2d6648, + 0x8cefbb02, + 0x8d60ff82, + 0x2c4e85, + 0x233b08, + 0x23c708, + 0x315b0c, + 0x237843, + 0x8da08842, + 0x8de00f02, + 0x3b9686, + 0x30df85, + 0x2dae43, + 0x252f86, + 0x30e0c6, + 0x27a383, + 0x310c83, + 0x311346, + 0x312bc4, + 0x263546, + 0x3b610a, + 0x23fbc4, + 0x313284, + 0x314aca, + 0x8e212f42, + 0x24cf45, + 0x31634a, + 0x316285, + 0x317c04, + 0x317d06, + 0x317e84, + 0x216486, + 0x8e615482, + 0x215486, + 0x251a45, + 0x3b2dc7, + 0x3b5f86, + 0x254f84, + 0x2db0c7, + 0x20a4c5, + 0x20a4c7, + 0x3b7147, + 0x3b714e, + 0x389606, + 0x22a785, + 0x205b07, + 0x207203, + 0x207207, + 0x21e905, + 0x225944, + 0x22a582, + 0x23db47, + 0x30b144, + 0x241b04, + 0x285f4b, + 0x21c283, + 0x2bc607, + 0x21c284, + 0x2bc907, + 0x229683, + 0x350f8d, + 0x3a0c48, + 0x8ea46384, + 0x246385, + 0x3194c5, + 0x319903, + 0x8ee25242, + 0x31c243, + 0x31d283, + 0x3b30c4, + 0x27cf45, + 0x2159c7, + 0x252146, + 0x38cdc3, + 0x22b34b, + 0x27350b, + 0x2ac28b, + 0x3cad8b, + 0x2e8fca, + 0x36f44b, + 0x39334b, + 0x3d950c, + 0x3dcb4b, + 0x31ddd1, + 0x31e20a, + 0x31e70b, + 0x31e9cc, + 0x31eccb, + 0x31ff4a, + 0x3206ca, + 0x321ece, + 0x32344b, + 0x32370a, + 0x324dd1, + 0x32520a, + 0x32570b, + 0x325c4e, + 0x326b4c, + 0x32738b, + 0x32764e, + 0x3279cc, + 0x32b40a, + 0x32c64c, + 0x8f32c94a, + 0x32d548, + 0x32e109, + 0x33204a, + 0x3322ca, + 0x33254b, + 0x334cce, + 0x335b91, + 0x341509, + 0x34174a, + 0x34244b, + 0x34634d, + 0x3471ca, + 0x348716, + 0x349a8b, + 0x34a80a, + 0x34ad8a, + 0x34c10b, + 0x34c989, + 0x351909, + 0x351e8d, + 0x35250b, + 0x35340b, + 0x353dcb, + 0x3543c9, + 0x354a0e, + 0x35520a, + 0x35608a, + 0x35698a, + 0x35724b, + 0x357a8b, + 0x35890d, + 0x35a04d, + 0x35a950, + 0x35ae0b, + 0x35b90c, + 0x35cecb, + 0x35ec0b, + 0x3602ce, + 0x3609cb, + 0x3609cd, + 0x36550b, + 0x365f8f, + 0x36634b, + 0x366b8a, + 0x3678c9, + 0x367f89, + 0x8f7689cb, + 0x368c8e, + 0x36900e, + 0x36cecb, + 0x36e00f, + 0x37024b, + 0x37050b, + 0x3707cb, + 0x370e8a, + 0x377889, + 0x37a00f, + 0x37e9cc, + 0x37ee0c, + 0x37f8ce, + 0x37fe4f, + 0x38020e, + 0x381310, + 0x38170f, + 0x3822ce, + 0x382e8c, + 0x383191, + 0x3835d2, + 0x384ad1, + 0x3852ce, + 0x38570b, + 0x38570e, + 0x385a8f, + 0x385e4e, + 0x3861d3, + 0x386691, + 0x386acc, + 0x386dce, + 0x38724c, + 0x387793, + 0x388650, + 0x38a34c, + 0x38a64c, + 0x38ab0b, + 0x38b0ce, + 0x38b5cb, + 0x38be8b, + 0x38d30c, + 0x39278a, + 0x392b4c, + 0x392e4c, + 0x393149, + 0x39494b, + 0x394c08, + 0x3953c9, + 0x3953cf, 0x396c8b, - 0x3d92cc, - 0x3da7cb, - 0x317351, - 0x31778a, - 0x317c8b, - 0x317f4c, - 0x31824b, - 0x3188ca, - 0x3191ca, - 0x31a0ce, - 0x31a98b, - 0x31ac4a, - 0x31c751, - 0x31cb8a, - 0x31d08b, - 0x31d5ce, - 0x31df0c, - 0x31e98b, - 0x31ec4e, - 0x31efcc, - 0x3222ca, - 0x32384c, - 0x8cf23b4a, - 0x324748, - 0x325309, - 0x333d0a, - 0x333f8a, - 0x33420b, - 0x3369ce, - 0x337651, - 0x342f49, - 0x34318a, - 0x343f8b, - 0x345aca, - 0x346f16, - 0x34828b, - 0x3498ca, - 0x349cca, - 0x34accb, - 0x34c109, - 0x34f1c9, - 0x34fc8d, - 0x35030b, - 0x35120b, - 0x351bcb, - 0x352709, - 0x352d4e, - 0x353dca, - 0x354c8a, - 0x3552ca, - 0x355b8b, - 0x3563cb, - 0x35724d, - 0x358bcd, - 0x359210, - 0x3596cb, - 0x35a24c, - 0x35b74b, - 0x35d48b, - 0x35eb4e, - 0x35f6cb, - 0x35f6cd, - 0x364c8b, - 0x36570f, - 0x365acb, - 0x36630a, - 0x368b49, - 0x369209, - 0x8d36a28b, - 0x36a54e, - 0x36e90b, - 0x36f58f, - 0x37208b, - 0x37234b, - 0x37260b, - 0x372e8a, - 0x378bc9, - 0x37d1cf, - 0x381d4c, - 0x38230c, - 0x38280e, - 0x382e0f, - 0x3831ce, - 0x383f10, - 0x38430f, - 0x384ece, - 0x385a8c, - 0x385d91, - 0x3861d2, - 0x388711, - 0x388f0e, - 0x38934b, - 0x38934e, - 0x3896cf, - 0x389a8e, - 0x389e13, - 0x38a2d1, - 0x38a70c, - 0x38aa0e, - 0x38ae8c, - 0x38b3d3, - 0x38bbd0, - 0x38d8cc, - 0x38dbcc, - 0x38e08b, - 0x38e64e, - 0x38eb4b, - 0x38f40b, - 0x3914cc, - 0x395f8a, - 0x39648c, - 0x39678c, - 0x396a89, - 0x3986cb, - 0x398988, - 0x399149, - 0x39914f, - 0x39a90b, - 0x8d79b24a, - 0x39d78c, - 0x39e94b, - 0x39ec09, - 0x39f388, - 0x39f94b, - 0x3a018b, - 0x3a0d0a, + 0x8fb9800a, + 0x399fcc, + 0x39b18b, + 0x39b449, + 0x39bbc8, + 0x39c18b, + 0x39c70b, + 0x39d28a, + 0x39d50b, + 0x39da0c, + 0x39e3c9, + 0x39e608, 0x3a0f8b, - 0x3a154c, - 0x3a2688, - 0x3a514b, - 0x3a80cb, - 0x3ab58e, - 0x3acccb, - 0x3af7cb, - 0x3bbbcb, - 0x3bbe89, - 0x3bc3cd, - 0x3ca50a, - 0x3cdb97, - 0x3cfd58, - 0x3d34c9, - 0x3d460b, - 0x3d50d4, - 0x3d55cb, - 0x3d5b4a, - 0x3d600a, - 0x3d628b, - 0x3d7250, - 0x3d7651, - 0x3d7c0a, - 0x3d88cd, - 0x3d8fcd, - 0x3dbd8b, - 0x3880c3, - 0x8db86943, - 0x2a9ec6, - 0x279c45, - 0x3911c7, - 0x334b06, - 0x1603a02, - 0x3d2049, - 0x3243c4, - 0x2e2608, - 0x21d503, - 0x313187, - 0x231442, - 0x2b1083, - 0x8de08c42, - 0x2cbb06, - 0x2ccf04, - 0x346604, - 0x36d203, - 0x8e6c8842, - 0x8ea1e244, - 0x276d87, - 0x8ee2ba82, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x1acac8, - 0x204783, + 0x3a3ccb, + 0x3a694e, + 0x3a7e4b, + 0x3aabcb, + 0x3b6ccb, + 0x3b6f89, + 0x3b74cd, + 0x3c9e8a, + 0x3cdd57, + 0x3cf418, + 0x3d41c9, + 0x3d530b, + 0x3d5814, + 0x3d5d0b, + 0x3d628a, + 0x3d694a, + 0x3d6bcb, + 0x3d7410, + 0x3d7811, + 0x3d7eca, + 0x3d8b0d, + 0x3d920d, + 0x3ddd8b, + 0x3b3043, + 0x8ff83d43, + 0x32af06, + 0x22ef05, + 0x307347, + 0x332e46, + 0x1602d42, + 0x2ab3c9, + 0x32d1c4, + 0x2e4d08, + 0x21dd43, + 0x319a87, + 0x22de82, + 0x2b05c3, + 0x902057c2, + 0x2cc446, + 0x2cdb04, + 0x347d04, + 0x346243, + 0x90ac91c2, + 0x90e2a444, + 0x273e47, + 0x9122a1c2, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x111148, + 0x20ca43, 0x2000c2, - 0xa7c88, - 0x203102, - 0x224943, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x202443, - 0x33fed6, - 0x362e53, - 0x21f009, - 0x245b48, - 0x3bd489, - 0x30f506, - 0x3505d0, - 0x243713, - 0x2e6148, - 0x27abc7, - 0x27c4c7, - 0x2a3d0a, - 0x3426c9, - 0x3a2809, - 0x25078b, - 0x326546, - 0x28938a, - 0x220c06, - 0x323fc3, - 0x2dab45, - 0x3b9a08, - 0x23620d, - 0x2031cc, - 0x2fd587, - 0x31a40d, - 0x222584, - 0x23224a, - 0x23348a, - 0x23394a, - 0x23cc87, - 0x23e247, - 0x240a84, - 0x2877c6, - 0x32b384, - 0x2d8588, - 0x3a9409, - 0x2ceb86, - 0x2ceb88, - 0x243ecd, - 0x2cb2c9, - 0x30ad48, - 0x22a747, - 0x23904a, - 0x251ec6, - 0x25dec7, - 0x2d0e84, - 0x28f047, - 0x22494a, - 0x23f84e, - 0x26fbc5, - 0x28ef4b, - 0x222b09, - 0x207349, - 0x371187, - 0x3dc44a, - 0x2c1387, - 0x2f38c9, - 0x209f08, - 0x32c60b, - 0x2e1985, - 0x22ca0a, - 0x225289, - 0x36beca, - 0x2cdccb, - 0x3c6c8b, - 0x250515, - 0x2e59c5, - 0x22a7c5, - 0x2f2d0a, - 0x25970a, - 0x313b87, - 0x22a803, - 0x2b8188, - 0x2d928a, - 0x222186, - 0x25b6c9, - 0x28b748, - 0x2e2e04, - 0x38b189, - 0x2c20c8, - 0x2bc707, - 0x2e7646, - 0x3858c7, - 0x2c2dc7, - 0x23fe85, - 0x26fa0c, - 0x23ba85, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x203102, - 0x22f743, - 0x20ce83, - 0x204783, - 0x23f7c3, - 0x22f743, - 0x20ce83, - 0x4783, - 0x26d843, - 0x23f7c3, - 0x1ca8c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0xa7c88, - 0x203102, - 0x22f743, - 0x22f747, - 0x20ce83, - 0x23f7c3, - 0x203102, + 0x9fe08, + 0x20c302, + 0x228b03, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, + 0x20c603, + 0x33b8d6, + 0x363e93, + 0x3d06c9, + 0x322c48, + 0x3b85c9, + 0x3164c6, + 0x3527d0, + 0x20c013, + 0x2ebc08, + 0x27a947, + 0x286b07, + 0x2a4a4a, + 0x3291c9, + 0x28f509, + 0x24224b, + 0x2e73c6, + 0x28830a, + 0x222a86, + 0x32cdc3, + 0x256545, + 0x3a8908, + 0x233fcd, + 0x21fd0c, + 0x2eaac7, + 0x3dcdcd, + 0x225744, + 0x22fd8a, + 0x23084a, + 0x230d0a, + 0x20c307, + 0x23a907, + 0x23da84, + 0x287806, + 0x251c04, + 0x2d58c8, + 0x32ad09, + 0x2cf506, + 0x2cf508, + 0x240dcd, + 0x2cbc09, + 0x3910c8, + 0x240907, + 0x23750a, + 0x24e606, + 0x25b7c7, + 0x2fb704, + 0x20b6c7, + 0x228b0a, + 0x239d4e, + 0x277f05, + 0x3d7c0b, + 0x22b709, + 0x24d8c9, + 0x2c6607, + 0x3c138a, + 0x2c2cc7, + 0x2f9309, + 0x27b408, + 0x2e640b, + 0x2e2a45, + 0x22ad8a, + 0x22b0c9, + 0x33e68a, + 0x20460b, + 0x20b5cb, + 0x241fd5, + 0x2c1f85, + 0x240985, + 0x2f67ca, + 0x3dbb0a, + 0x31b687, + 0x233c43, + 0x2ba388, + 0x2d80ca, + 0x225346, + 0x2592c9, + 0x28a748, + 0x2dedc4, + 0x387549, + 0x2c3748, + 0x2c2407, + 0x382d06, + 0x3c9447, + 0x2b4607, + 0x23ca85, + 0x2e450c, + 0x246385, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0xca43, + 0x216603, + 0x20c302, + 0x22c0c3, + 0x20a803, + 0x20ca43, + 0x216603, + 0x22c0c3, + 0x20a803, + 0xca43, + 0x239503, + 0x216603, + 0x1ca243, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, + 0x9fe08, + 0x20c302, + 0x22c0c3, + 0x22c0c7, + 0x20a803, + 0x216603, + 0x20c302, 0x201d02, - 0x2e63c2, - 0x2051c2, - 0x2090c2, - 0x2e4e02, - 0x92246, - 0x52449, - 0x481bbc3, - 0x891c7, - 0x50c3, - 0x112585, + 0x2ebe82, + 0x202cc2, + 0x202f82, + 0x2e5382, + 0x91746, + 0x4e9c9, + 0x481b443, + 0x88147, + 0x5b03, + 0x119045, 0xc1, - 0x522f743, - 0x234e83, - 0x20d343, - 0x224943, - 0x214503, - 0x211d83, - 0x2daa46, - 0x20ce83, - 0x23f7c3, - 0x2021c3, - 0xa7c88, - 0x3314c4, - 0x332e47, - 0x36d243, - 0x2e8204, - 0x2186c3, - 0x287e03, - 0x224943, - 0xe747, + 0x522c0c3, + 0x232c43, + 0x212483, + 0x228b03, + 0x214543, + 0x211543, + 0x2d9fc6, + 0x20a803, + 0x216603, + 0x20f803, + 0x9fe08, + 0x3443c4, + 0x374787, + 0x346283, + 0x2b3904, + 0x218c83, + 0x286243, + 0x228b03, + 0x176c87, 0x9c4, 0x11c3, - 0x1273c5, + 0x2b05, 0x2000c2, - 0x4af03, - 0x6603102, - 0x688b549, - 0x8bbcd, - 0x8cb4d, - 0x2e63c2, - 0x21b84, - 0x127409, + 0x48343, + 0x660c302, + 0x688a549, + 0x8abcd, + 0x8af0d, + 0x2ebe82, + 0x24e44, + 0x2b49, 0x2003c2, - 0x6e21a88, - 0xf2484, - 0xa7c88, - 0x1416702, + 0x6e24d48, + 0xf61c4, + 0x9fe08, + 0x1417c42, 0x14005c2, - 0x1416702, - 0x150c906, - 0x2314c3, - 0x271803, - 0x762f743, - 0x232244, - 0x7a34e83, - 0x8624943, - 0x203842, - 0x221b84, - 0x20ce83, - 0x301e03, + 0x1417c42, + 0x1513486, + 0x22df03, + 0x26fb43, + 0x762c0c3, + 0x22fd84, + 0x7a32c43, + 0x8628b03, + 0x203dc2, + 0x224e44, + 0x20a803, + 0x2e59c3, 0x201e02, - 0x23f7c3, - 0x218002, - 0x2faa83, - 0x205fc2, - 0x2a3903, - 0x28b803, - 0x202d02, - 0xa7c88, - 0x2314c3, - 0x20bb08, - 0x2802c2, - 0x7f01e03, + 0x216603, + 0x2185c2, + 0x301e43, + 0x204e82, + 0x203303, + 0x28a803, + 0x205842, + 0x9fe08, + 0x22df03, + 0x3c38c8, + 0x7ee59c3, 0x201e02, - 0x2faa83, - 0x205fc2, - 0x82a3903, - 0x28b803, - 0x202d02, - 0x254c07, - 0x234e89, - 0x2faa83, - 0x205fc2, - 0x2a3903, - 0x28b803, - 0x202d02, - 0x22f743, - 0x24af03, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x214503, - 0x211d83, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x20da42, - 0x214e03, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x24af03, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x20ce83, - 0x23f7c3, - 0x207ac5, - 0x210782, + 0x301e43, + 0x204e82, + 0x8203303, + 0x28a803, + 0x205842, + 0x253c47, + 0x232c49, + 0x301e43, + 0x204e82, + 0x203303, + 0x28a803, + 0x205842, + 0x22c0c3, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x214543, + 0x211543, + 0x217b84, + 0x20a803, + 0x216603, + 0x20b142, + 0x216103, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x248343, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x20a803, + 0x216603, + 0x204185, + 0x20ff42, 0x2000c2, - 0xa7c88, - 0x14441c8, - 0x13258a, - 0x224943, - 0x2042c1, + 0x9fe08, + 0x14470c8, + 0xf704a, + 0x228b03, + 0x203281, 0x2009c1, 0x200a01, 0x201301, 0x201281, - 0x205ac1, - 0x202881, - 0x220541, - 0x204341, + 0x207101, + 0x2027c1, + 0x2223c1, + 0x203bc1, 0x200001, 0x2000c1, 0x200201, - 0x129245, - 0xa7c88, + 0x12eb85, + 0x9fe08, 0x200101, 0x2015c1, 0x200501, @@ -2347,7067 +2337,7112 @@ var nodes = [...]uint32{ 0x200581, 0x2003c1, 0x200a81, - 0x216b41, + 0x209101, 0x200401, 0x200741, 0x2007c1, 0x200081, 0x200f01, - 0x202d01, + 0x205841, 0x201241, 0x2018c1, - 0x2086c1, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x203102, - 0x22f743, - 0x234e83, + 0x204981, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x20c302, + 0x22c0c3, + 0x232c43, 0x2003c2, - 0x23f7c3, - 0x1b583, - 0xe747, - 0x11e87, - 0x25786, - 0x36a8a, - 0x8ac88, - 0x56548, - 0x57007, - 0x1757c6, - 0xe0a45, - 0x1339c5, - 0x11d483, - 0x1c6b46, - 0xfd986, - 0x250784, - 0x3bd8c7, - 0xa7c88, - 0x2dbb04, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x3102, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x323d48, - 0x330684, - 0x234dc4, - 0x226804, - 0x367a07, - 0x2d7607, - 0x22f743, - 0x2375cb, - 0x316cca, - 0x326807, - 0x302288, - 0x32aac8, - 0x234e83, - 0x32d787, - 0x20d343, - 0x2083c8, - 0x20b5c9, - 0x221b84, - 0x214503, - 0x23b2c8, - 0x211d83, - 0x2d430a, - 0x2daa46, - 0x3aa987, - 0x20ce83, - 0x398446, - 0x271688, - 0x23f7c3, - 0x258f86, - 0x2e9b0d, - 0x2ec208, - 0x2f304b, - 0x3c6506, - 0x368587, - 0x212885, - 0x22914a, - 0x220205, - 0x23444a, - 0x210782, - 0x2050c3, - 0x2ff104, + 0x216603, + 0x1ae03, + 0x176c87, + 0x11647, + 0x39346, + 0x3484a, + 0x89988, + 0x53388, + 0x53f47, + 0xbdc06, + 0xe1145, + 0x175305, + 0x125b03, + 0x13686, + 0x3e006, + 0x242244, + 0x2f6d07, + 0x9fe08, + 0x2db1c4, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0xc302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x32cb48, + 0x345344, + 0x232b84, + 0x26bb44, + 0x3b9587, + 0x2d6b87, + 0x22c0c3, + 0x23560b, + 0x37400a, + 0x2ff007, + 0x308a08, + 0x2f7b48, + 0x232c43, + 0x2e6687, + 0x212483, + 0x204f48, + 0x209c89, + 0x224e44, + 0x214543, + 0x238cc8, + 0x211543, + 0x2d378a, + 0x2d9fc6, + 0x3a5d47, + 0x20a803, + 0x373846, + 0x26f9c8, + 0x216603, + 0x2433c6, + 0x2edf0d, + 0x2f0348, + 0x2f8a8b, + 0x20b186, + 0x3baac7, + 0x212605, + 0x3c554a, + 0x222085, + 0x2410ca, + 0x20ff42, + 0x205b03, + 0x241b04, 0x200006, - 0x3b2f83, - 0x2a7b83, - 0x24e8c3, - 0x2c5d43, - 0x2b5903, - 0x203082, - 0x2fcf85, - 0x2a9749, - 0x240503, - 0x21c183, - 0x224cc3, + 0x3ae643, + 0x29fd03, + 0x258783, + 0x20f643, + 0x373c83, + 0x203582, + 0x3abe85, + 0x2aad89, + 0x23d103, + 0x225843, + 0x215203, 0x200201, - 0x2e6f07, - 0x2d15c5, - 0x3a8343, - 0x3c6083, - 0x226804, - 0x327003, - 0x20f588, - 0x368d83, - 0x3097cd, - 0x38cc48, - 0x20bcc6, - 0x329143, - 0x391783, - 0x3acf43, - 0xc62f743, - 0x2346c8, - 0x2375c4, - 0x241183, + 0x2cfb07, + 0x2e0605, + 0x3a8843, + 0x3c7543, + 0x26bb44, + 0x2fef83, + 0x21b908, + 0x367b03, + 0x30f30d, + 0x3896c8, + 0x3c3a86, + 0x2fdec3, + 0x360cc3, + 0x38d5c3, + 0xc62c0c3, + 0x232488, + 0x235604, + 0x23e483, 0x200106, - 0x244988, - 0x27d8c3, - 0x229183, - 0x22f8c3, - 0x234e83, - 0x2199c3, - 0x250603, - 0x286583, - 0x3290c3, - 0x28e243, - 0x2227c3, - 0x38f685, - 0x252404, - 0x252d87, - 0x2b1d82, - 0x2562c3, - 0x259106, - 0x25ab43, - 0x25b203, - 0x279e43, - 0x376083, - 0x3311c3, - 0x298c07, - 0xca24943, - 0x249203, - 0x3d66c3, - 0x2083c3, - 0x214343, - 0x2f8683, - 0x3b9485, - 0x374ec3, + 0x241648, + 0x27c083, + 0x3c5583, + 0x22c243, + 0x232c43, + 0x2234c3, + 0x2420c3, + 0x284ac3, + 0x3313c3, + 0x28c603, + 0x20d643, + 0x38c105, + 0x24e984, + 0x24f307, + 0x2b12c2, + 0x252d83, + 0x256cc6, + 0x258283, + 0x258e03, + 0x277e03, + 0x2be4c3, + 0x3440c3, + 0x297e47, + 0xca28b03, + 0x2530c3, + 0x3d9e83, + 0x204f43, + 0x214383, + 0x2157c3, + 0x3c3345, + 0x372f03, 0x200e09, 0x201503, - 0x304c43, - 0xce37bc3, - 0x2bd483, - 0x21acc8, - 0x2a9686, - 0x375e46, - 0x29e046, - 0x38c287, - 0x222e43, - 0x225a83, - 0x211d83, - 0x28ad86, - 0x203282, - 0x2a2c03, - 0x33e985, - 0x20ce83, - 0x3104c7, - 0x1604783, - 0x271683, - 0x207c83, - 0x212ec3, - 0x209b43, - 0x23f7c3, - 0x20c586, - 0x326d46, - 0x37da83, - 0x2eb003, - 0x214e03, - 0x21c283, - 0x30a443, - 0x2f8c83, - 0x2fae43, - 0x216f45, - 0x259703, - 0x387b06, - 0x32b588, - 0x212c03, - 0x3be389, - 0x363348, - 0x215a48, - 0x21fc05, - 0x2ff34a, - 0x35fcca, - 0x3a128b, - 0x22ecc8, - 0x2ed043, - 0x390c43, - 0x2f2f83, - 0x305a48, - 0x3788c3, - 0x32ee04, - 0x20b102, - 0x25e943, + 0x30b703, + 0xce35c03, + 0x2c9343, + 0x219a08, + 0x2aacc6, + 0x2be286, + 0x2b1306, + 0x388d07, + 0x228503, + 0x238cc3, + 0x211543, + 0x289a86, + 0x21fdc2, + 0x28f543, + 0x33a385, + 0x20a803, + 0x316d07, + 0x160ca43, + 0x26f9c3, + 0x204343, + 0x230743, + 0x21d283, + 0x216603, + 0x20dc46, + 0x3b9d86, + 0x37a8c3, + 0x2ef183, + 0x216103, + 0x2275c3, + 0x310d03, + 0x2fd203, + 0x302203, + 0x3973c5, + 0x236bc3, + 0x236bc6, + 0x211f08, + 0x212983, + 0x212989, + 0x33f908, + 0x216f88, + 0x221105, + 0x22ceca, + 0x22e04a, + 0x237acb, + 0x23a5c8, + 0x2ed803, + 0x38cf03, + 0x2f9503, + 0x30e208, + 0x3606c3, + 0x2520c4, + 0x207442, + 0x25c283, 0x2007c3, - 0x222d03, - 0x254f83, - 0x2021c3, - 0x210782, - 0x22ab43, - 0x239383, - 0x30ca83, - 0x30e284, - 0x2ff104, - 0x3c6903, - 0xa7c88, + 0x228803, + 0x250d43, + 0x20f803, + 0x20ff42, + 0x229543, + 0x237843, + 0x313603, + 0x315504, + 0x241b04, + 0x3ced43, + 0x9fe08, 0x2000c2, 0x200ac2, - 0x203082, - 0x202602, + 0x203582, + 0x202542, 0x200202, 0x201ec2, - 0x255002, + 0x25a902, 0x201bc2, 0x200382, 0x200c42, - 0x32a2c2, - 0x204fc2, - 0x26ff42, + 0x252742, + 0x205a02, + 0x26bf42, 0x200a82, - 0x2e4e02, - 0x204ac2, + 0x2e5382, + 0x2161c2, 0x201c82, - 0x214e02, - 0x2b2002, - 0x2046c2, + 0x216102, + 0x228702, + 0x204d42, 0x200682, - 0x215742, - 0x203e42, + 0x216c82, 0x202442, - 0x205c82, - 0x234542, + 0x209482, + 0x202e42, + 0x2411c2, 0x201942, 0xc2, 0xac2, - 0x3082, - 0x2602, + 0x3582, + 0x2542, 0x202, 0x1ec2, - 0x55002, + 0x5a902, 0x1bc2, 0x382, 0xc42, - 0x12a2c2, - 0x4fc2, - 0x6ff42, + 0x52742, + 0x5a02, + 0x6bf42, 0xa82, - 0xe4e02, - 0x4ac2, + 0xe5382, + 0x161c2, 0x1c82, - 0x14e02, - 0xb2002, - 0x46c2, + 0x16102, + 0x28702, + 0x4d42, 0x682, - 0x15742, - 0x3e42, + 0x16c82, 0x2442, - 0x5c82, - 0x34542, + 0x9482, + 0x2e42, + 0x411c2, 0x1942, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x50c2, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x3102, - 0x203102, - 0x23f7c3, - 0xe62f743, - 0x224943, - 0x211d83, - 0x71003, - 0x228002, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x8c42, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x5b02, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0xc302, + 0x20c302, + 0x216603, + 0xe62c0c3, + 0x228b03, + 0x211543, + 0x6d9c3, + 0x22d7c2, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x57c2, 0x2001c2, - 0x143c845, - 0x129245, - 0x20bac2, - 0xa7c88, - 0x3102, - 0x2366c2, - 0x204482, - 0x20f782, - 0x211cc2, - 0x24dc02, - 0x1339c5, + 0x154da85, + 0x12eb85, + 0x208602, + 0x9fe08, + 0xc302, + 0x234482, + 0x204b02, + 0x21bb02, + 0x211482, + 0x239242, + 0x175305, 0x2016c2, 0x201e02, - 0x218782, + 0x218d42, 0x201dc2, - 0x204ac2, - 0x3a1642, + 0x2161c2, + 0x23d182, 0x201782, - 0x296d02, - 0xf731fc4, + 0x296542, + 0xf73f344, 0x142, - 0xe747, - 0x14f80d, - 0xe0ac9, - 0xd700b, - 0xe2f48, - 0x668c9, - 0x105546, - 0x224943, - 0xa7c88, + 0x176c87, + 0x1266cd, + 0xe11c9, + 0x11214b, + 0xe8188, + 0x643c9, + 0x10c246, + 0x228b03, + 0x9fe08, 0x9c4, 0x11c3, - 0x1273c5, - 0xa7c88, - 0xde387, - 0x57b06, - 0x127409, - 0xe50e, - 0x14bf47, + 0x2b05, + 0x9fe08, + 0xddc47, + 0x54c46, + 0x2b49, + 0x7b0e, + 0x14a647, 0x2000c2, - 0x250784, - 0x203102, - 0x22f743, + 0x242244, + 0x20c302, + 0x22c0c3, 0x201d02, - 0x234e83, - 0x143c3, + 0x232c43, + 0x14403, 0x200382, - 0x2dbb04, - 0x214503, - 0x249602, - 0x20ce83, + 0x2db1c4, + 0x214543, + 0x244502, + 0x20a803, 0x2003c2, - 0x23f7c3, - 0x22a7c6, - 0x3347cf, + 0x216603, + 0x240986, + 0x332b0f, 0x602, - 0x776243, - 0xa7c88, - 0x203102, - 0x20d343, - 0x224943, - 0x211d83, - 0x4783, - 0xe508, - 0x1428f0b, - 0x1540cca, - 0x152bd0a, - 0x1427bc7, - 0xa3dcb, - 0x178945, - 0x110209, - 0x129245, - 0xe747, - 0xef6c4, - 0x203102, - 0x22f743, - 0x224943, - 0x20ce83, + 0x6be683, + 0x9fe08, + 0x20c302, + 0x212483, + 0x228b03, + 0x211543, + 0xca43, + 0x7b08, + 0x15c2c4b, + 0x15642ca, + 0xf5009, + 0x15c534a, + 0x150bb87, + 0xa4b0b, + 0x160745, + 0x116a49, + 0x12eb85, + 0x176c87, + 0xf3644, + 0x20c302, + 0x22c0c3, + 0x228b03, + 0x20a803, 0x2000c2, 0x200c82, - 0x33d3c2, - 0x12a2f743, - 0x23e8c2, - 0x234e83, + 0x338dc2, + 0x12a2c0c3, + 0x23b182, + 0x232c43, 0x2014c2, - 0x2268c2, - 0x224943, - 0x203942, - 0x28bec2, - 0x21e202, + 0x26bc02, + 0x228b03, + 0x220482, + 0x288542, + 0x22a402, 0x200cc2, - 0x291f82, + 0x291482, 0x200802, 0x200d82, - 0x25ce02, - 0x27bc02, - 0x216842, - 0x15388c, - 0x2b2a02, - 0x2ea082, - 0x214682, - 0x242bc2, - 0x211d83, - 0x200bc2, - 0x20ce83, - 0x224a82, - 0x2d33c2, - 0x23f7c3, - 0x240582, - 0x202442, - 0x214d02, 0x2033c2, - 0x210b82, - 0x2e4542, - 0x214642, - 0x21ddc2, - 0x21d9c2, - 0x31ac4a, - 0x36630a, - 0x39c18a, - 0x3dd442, - 0x20fd02, - 0x3b9442, - 0x12e45889, - 0x13363c4a, - 0x1430787, - 0x13605102, - 0x14ed383, - 0x2542, - 0x163c4a, - 0x18b84e, - 0x23d544, - 0x77605, - 0x13e2f743, - 0x3f0c3, - 0x234e83, - 0x24f044, - 0x224943, - 0x221b84, - 0x214503, - 0x1405c9, - 0x1a2ac6, - 0x211d83, - 0xe3e84, - 0x176943, - 0x20ce83, - 0x7085, - 0x204783, - 0x23f7c3, - 0x143d444, - 0x259703, - 0x1069c4, - 0x2050c3, - 0xa7c88, - 0x176186, - 0x1575744, - 0x16d005, - 0x14bd0a, - 0x1237c2, - 0x1a7ac6, - 0x55e11, - 0x14645889, - 0x16d088, - 0x4e388, - 0x1c7bc7, - 0x3002, - 0xe31ce, - 0x12924b, - 0x1355cb, - 0x19c68a, - 0x892ca, - 0x26807, - 0xa7c88, - 0x114748, - 0x4e87, - 0x1b01530b, - 0x1b587, - 0x1c782, - 0x5d4c7, - 0x27f0a, - 0x5588f, - 0x3cb4f, - 0x291c2, - 0x3102, - 0x864c8, - 0xe3aca, - 0xdde8a, - 0x52b0a, - 0x18ca48, - 0xc288, - 0x5bb48, - 0xde348, - 0x16c848, - 0x7682, - 0x3c8cf, - 0xa1f8b, - 0x18d648, - 0x39dc7, + 0x21efc2, + 0x217d82, + 0xe550c, + 0x2b2182, + 0x2f10c2, + 0x215982, + 0x2450c2, + 0x211543, + 0x200bc2, + 0x20a803, + 0x228c42, + 0x25b002, + 0x216603, + 0x301b82, + 0x209482, + 0x216002, + 0x203382, + 0x210342, + 0x2e8f82, + 0x214682, + 0x22b902, + 0x21e202, + 0x32370a, + 0x366b8a, + 0x39914a, + 0x3de682, + 0x20e6c2, + 0x3c3302, + 0x12fda689, + 0x1328caca, + 0x142d1c7, + 0x136049c2, + 0x14bd543, + 0x2482, + 0x8caca, + 0x15d14e, + 0x249f84, + 0xf04c5, + 0x13e2c0c3, + 0x3b903, + 0x232c43, + 0x24b304, + 0x228b03, + 0x224e44, + 0x214543, + 0x13bfc9, + 0x8f7c6, + 0x211543, + 0xe88c4, + 0x2143, + 0x20a803, + 0x12abc5, + 0x20ca43, + 0x216603, + 0x1429b04, + 0x236bc3, + 0x10cbc4, + 0x205b03, + 0x9fe08, + 0xbe5c6, + 0x14bdb84, + 0x146045, + 0x14a40a, + 0x12c5c2, + 0x1454114d, + 0x1a36c6, + 0x8f11, + 0x14bda689, + 0x1460c8, + 0x4aa08, + 0x1b584807, + 0x2282, + 0x1da807, + 0xe840e, + 0x12eb8b, + 0x13390b, + 0x1a3f4a, + 0x8824a, + 0x6bb47, + 0x9fe08, + 0x11a9c8, + 0x58c7, + 0x1b81684b, + 0x1ae07, + 0x9582, + 0x2b80d, + 0x13e387, + 0x14ebca, + 0x1ce74f, + 0x1732cf, + 0x8cac2, + 0xc302, + 0x84a08, + 0x1bcfdc4c, + 0xe1cca, + 0xdd74a, + 0x4f08a, + 0x1894c8, + 0xd948, + 0x59748, + 0xddc08, + 0xe7688, + 0x81c2, + 0x14db0f, + 0xa24cb, + 0x18a0c8, + 0x67607, 0x168a, - 0x13a0cb, - 0x35309, - 0x49547, - 0xc188, - 0x15fe8c, - 0x1385c7, - 0xdad4a, - 0xfcc8, - 0x2578e, - 0x25f4e, - 0x2664b, - 0x296cb, - 0x2c0cb, - 0x2cf89, - 0x6cb8b, - 0x6d68d, - 0xdc28b, - 0xf990d, - 0xf9c8d, - 0xfd7ca, - 0xfef4b, - 0x3b8cb, - 0x3f545, - 0x1b424010, - 0x174cf, - 0x13218f, - 0x6e90d, - 0x140790, - 0x69102, - 0x1ba1ed48, - 0x11d08, - 0xa1150, - 0x11578e, - 0x1bf68d45, - 0x4e18b, - 0x13f6d0, - 0x55548, - 0xc38a, - 0x29889, - 0x63007, - 0x63347, - 0x63507, - 0x63d47, - 0x65147, + 0x11404b, + 0x330c9, + 0x44447, + 0xd848, + 0x2e20c, + 0x16c307, + 0x5674a, + 0xe688, + 0x6aace, + 0x6b28e, + 0x6b98b, + 0x1d2e8b, + 0x1cb64b, + 0xe3089, + 0xead0b, + 0x3934d, + 0x3b98b, + 0x3c10d, + 0x3c48d, + 0x3de4a, + 0x4194b, + 0x461cb, + 0x44845, + 0x1c026590, + 0x19794f, + 0x13f50f, + 0x6a44d, + 0x13c190, + 0x9802, + 0x1c7d0408, + 0x114c8, + 0x93110, + 0x11d30e, + 0x1cb67ac5, + 0x4a80b, + 0x13b0d0, + 0x515c8, + 0xda4a, + 0x1d3049, + 0x60907, + 0x60c47, + 0x60e07, + 0x61787, + 0x62b87, + 0x63107, + 0x63b87, + 0x640c7, + 0x64d07, + 0x65087, 0x65747, - 0x66087, - 0x665c7, - 0x67107, - 0x67487, - 0x67b47, - 0x67d07, - 0x67ec7, - 0x68087, - 0x69207, - 0x69ac7, - 0x6ac47, - 0x6b007, - 0x6b647, - 0x6b907, - 0x6bac7, - 0x6bdc7, - 0x6fe07, - 0x70007, - 0x703c7, - 0x70587, - 0x70747, - 0x71387, - 0x72987, - 0x72e07, - 0x73907, - 0x73bc7, - 0x73f47, - 0x74107, - 0x74507, - 0x755c7, - 0x75a07, - 0x75f87, - 0x76147, - 0x76307, - 0x76b87, - 0x77747, - 0x77c87, - 0x78207, - 0x783c7, - 0x78747, - 0x79347, - 0x4582, - 0x5bc4a, - 0xe3fc7, - 0x888c5, - 0xa6411, - 0x1b9e06, - 0xf5a8a, - 0x8634a, - 0x57b06, - 0xacf8b, - 0x642, - 0x31b11, - 0x1522c9, - 0x98009, - 0x5ce02, - 0x8268a, - 0xa8c49, - 0xa938f, - 0xa998e, - 0xac2c8, - 0x5dc2, - 0x175c89, - 0x19b74e, - 0x1c1f4c, - 0xe534f, - 0x1b380e, - 0x2b3cc, - 0x6d309, - 0x6ee11, - 0x6f3c8, - 0x139312, - 0x13a8cd, - 0x16028d, - 0x172b4b, - 0x4e555, - 0x53889, - 0x5ac8a, - 0x5f309, - 0x74a50, - 0x7574b, - 0x8678f, - 0x171dcb, - 0x1c998c, - 0x91ad0, - 0x9748a, - 0xa2acd, - 0xabf4e, - 0xadcca, - 0xb00cc, - 0xc2a94, - 0x151f51, - 0xb5b0b, - 0xb7d0f, - 0xb970d, - 0xb9f8e, - 0xbc5cc, - 0xbe58c, - 0xc278b, - 0xc334e, - 0xc3c10, - 0xc4a0b, - 0x170d8d, - 0x14388f, - 0xcbe4c, - 0xcea0e, - 0xcfc11, - 0xd830c, - 0xe0e07, - 0xeea0d, - 0xf6c0c, - 0x108c10, - 0x1ccf4d, - 0xfffc7, - 0x145d10, - 0x166548, - 0x1730cb, - 0xb184f, - 0x120648, - 0xf5c8d, - 0x103a10, - 0x17ef89, - 0x1c2b36c6, - 0xb4d43, - 0xba545, - 0x51b42, - 0x1b09, - 0x584ca, - 0x1c7397c6, - 0x1ca5d6c4, - 0x58e46, - 0x1d40a, - 0x81b0d, - 0x1cd68009, - 0x166c3, - 0x106cca, - 0xdd091, - 0xdd4c9, - 0xdde07, - 0xdeb88, - 0xdf187, - 0xe4088, - 0x3c9cb, - 0x125b89, - 0xe4810, - 0xe4ccc, - 0xe5808, - 0xe5c85, - 0xa088, - 0x10630a, - 0x139147, - 0x129f47, - 0x2f42, - 0x1403ca, - 0x14aac8, - 0x1c5009, - 0x79d05, - 0x11034a, - 0x8f30f, - 0x12284b, - 0x1b954c, - 0x146252, - 0x7ce85, - 0xe7948, - 0x4f34a, - 0x1d2f1585, - 0x1866cc, - 0x13d203, - 0x1a1642, - 0xfafcb, - 0xfc88a, - 0x14fcc0c, - 0x138948, - 0xf9ac8, - 0x1d74ab46, + 0x65907, + 0x65ac7, + 0x65c87, + 0x664c7, + 0x67907, 0x688c7, - 0x129c2, - 0x5fc2, - 0x4cb90, - 0x69387, - 0x3128f, - 0x1c6b46, - 0x15b10e, - 0x9640b, - 0x18fe88, - 0x356c9, - 0x17bf92, - 0x10ac0d, - 0x10b488, - 0xd6ec9, - 0x14c80d, - 0x19a249, - 0x5c8b, - 0x6bf48, + 0x68c87, + 0x692c7, + 0x69587, + 0x69747, + 0x69a47, + 0x6be07, + 0x6c007, + 0x6cd87, + 0x6cf47, + 0x6d107, + 0x6f6c7, + 0x70a87, + 0x70f07, + 0x71647, + 0x71907, + 0x71c87, + 0x71e47, + 0x72247, + 0x72687, + 0x72a87, + 0x73007, + 0x731c7, + 0x73387, + 0x73c47, + 0x746c7, + 0x74c07, + 0x75207, + 0x753c7, + 0x75747, + 0x76847, + 0x4c02, + 0x5984a, + 0x13488, + 0x11a007, + 0x8a005, + 0xa7791, + 0x4886, + 0xfadca, + 0x8488a, + 0x54c46, + 0xacd8b, + 0x642, + 0x2f451, + 0xb6fc9, + 0x97249, + 0x33c2, + 0x198c0a, + 0xaa289, + 0xaa9cf, + 0xaafce, + 0xac0c8, + 0x4742, + 0xbe0c9, + 0x1b0ece, + 0xea24c, + 0xf4d0f, + 0x1aeece, + 0x26ccc, + 0xe3389, + 0xe3911, + 0xe3ec8, + 0x2e612, + 0x17f34d, + 0x1a054d, + 0x4abcb, + 0x58415, + 0x6dc89, + 0x7280a, + 0x7a089, + 0x80510, + 0x16ff8b, + 0x1a798f, + 0x9110b, + 0x9494c, + 0x99310, + 0xa9a4a, + 0xadccd, + 0xaf68e, + 0xafdca, + 0xc4a8c, + 0xb42d4, + 0xb6c51, + 0xb8e0b, + 0xb9f0f, + 0xbb38d, + 0xbf5ce, + 0xc22cc, + 0xc3e0c, + 0xc478b, + 0xc558e, + 0xc6110, + 0xc7dcb, + 0x11c78d, + 0x141d4f, + 0x16d38c, + 0xcf38e, + 0xd1151, + 0xd564c, + 0xde6c7, + 0xf2b0d, + 0xfc10c, + 0x110450, + 0x1cc44d, + 0x105687, + 0x147410, + 0x166dc8, + 0x1710cb, + 0xb0d8f, + 0x1432c8, + 0xfafcd, + 0x10a190, + 0x176b89, + 0x1ceb2d46, + 0xb3cc3, + 0xb90c5, + 0x4e082, + 0x1b09, + 0x5590a, + 0x1d22eac6, + 0x1d73e584, + 0x56286, + 0x1dc4a, + 0x6f0cd, + 0x1d9b9b89, + 0x17c03, + 0x11594a, + 0xdb911, + 0xdbd49, + 0xdd6c7, + 0xde408, + 0xde887, + 0x11a0c8, + 0x908b, + 0x12e989, + 0xe9250, + 0xe970c, + 0xea548, + 0xea8c5, 0x79148, - 0x7ccc8, - 0x7ffc9, - 0x801ca, - 0x872cc, - 0x3bb8a, - 0xe898a, - 0x10a487, - 0x4840d, - 0x6f6d1, - 0x1daba286, - 0x1b1c4b, - 0x1241cc, - 0x2eb08, - 0x47309, - 0x18058d, - 0x1a7cd0, - 0x17dbcd, - 0x13602, - 0x66dcd, - 0x4cc2, - 0x187c2, - 0x10a3ca, - 0x124ca, - 0xf598a, - 0x11308b, - 0x261cc, - 0x11424a, - 0x1144ce, - 0x18820d, - 0x1dddd305, - 0x12d5c8, - 0x8c42, - 0x14b7704e, - 0x15203f4e, - 0x15a0260a, - 0x1636ccce, - 0x16b21f4e, - 0x172fbe8c, - 0x1430787, - 0x1430789, - 0x14ed383, - 0x17bbeccc, - 0x18342d09, - 0x18b44d49, - 0x1934bb09, - 0x2542, - 0x176f91, - 0x3e91, - 0x254d, - 0x16cc11, - 0x121e91, - 0xfbdcf, - 0x1bec0f, - 0x142c4c, - 0x144c8c, - 0x14ba4c, - 0x16880d, - 0x6a395, - 0x7eb8c, - 0x135ccc, - 0x17ca50, - 0x1947cc, - 0x1a9b4c, - 0x1ae759, - 0x1b4c99, - 0x1b6c19, - 0x1bb6d4, - 0x1c3194, - 0x1c42d4, - 0x1c5614, - 0x5014, - 0x19a7ec49, - 0x1a1c4589, - 0x1ab35d89, - 0x14e6f5c9, - 0x2542, - 0x1566f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x15e6f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x1666f5c9, - 0x2542, - 0x16e6f5c9, - 0x2542, - 0x1766f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x17e6f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x1866f5c9, - 0x2542, - 0x18e6f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x1966f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x19e6f5c9, - 0x2542, - 0x1a66f5c9, - 0x2542, - 0x1ae6f5c9, - 0x2542, - 0x500a, - 0x2542, - 0x55e05, - 0x19c684, - 0x17704e, - 0x3f4e, - 0x7b4ce, - 0x260a, - 0x16ccce, - 0x121f4e, - 0xfbe8c, - 0x1beccc, - 0x142d09, - 0x144d49, - 0x14bb09, - 0x7ec49, - 0x1c4589, - 0x135d89, - 0x6a58d, - 0x194889, - 0x52c9, - 0x1497c4, - 0x1d7144, - 0x69104, - 0x154b84, - 0xa4084, - 0x2dd44, - 0x37dc4, - 0x4df04, - 0xfe484, - 0x15a1203, - 0xe3687, - 0x35fcc, - 0x31643, - 0x69102, - 0x188203, - 0x31643, - 0x90f43, - 0x7842, - 0x7848, - 0x125c07, - 0x7682, + 0x10eaca, + 0xc7c07, + 0x523c7, + 0x2f42, + 0x1de46bd5, + 0x13bdca, + 0x3d888, + 0x98b89, + 0x2efc5, + 0x116b8a, + 0x8d04f, + 0x12b98b, + 0x1c340c, + 0x147952, + 0x78005, + 0x1966c8, + 0x4b60a, + 0x1e2f5b85, + 0x183acc, + 0x138c03, + 0x190ec6, + 0x3d182, + 0x10238b, + 0x102dca, + 0x150314c, + 0x11848, + 0x3c2c8, + 0x1e63d906, + 0x12fec7, + 0xa982, + 0x4e82, + 0x4bdd0, + 0x66647, + 0x2dccf, + 0x13686, + 0x15c7ce, + 0x9588b, + 0x45948, + 0x33489, + 0xfe992, + 0x190f8d, + 0x111788, + 0x112009, + 0x17ab8d, + 0x1964c9, + 0x1d714b, + 0x69bc8, + 0x7ea48, + 0x80908, + 0x80d49, + 0x80f4a, + 0x8730c, + 0x4648a, + 0xec2ca, + 0x110d47, + 0x9f70a, + 0x13670d, + 0xe41d1, + 0x1eabf8c6, + 0x1ab70b, + 0x12cfcc, + 0x37908, + 0x61249, + 0x15b14d, + 0x1a38d0, + 0x17b8cd, + 0x14302, + 0x5cd8d, + 0x5402, + 0x18d82, + 0x110c8a, + 0x1d4ca, + 0xfacca, + 0x11998b, + 0x6b50c, + 0x11a4ca, + 0x11a74e, + 0x1b318d, + 0x1edde545, + 0x1dae88, + 0x57c2, + 0x14e85c3, + 0x14ebec0e, + 0x156037ce, + 0x15e0254a, + 0x16745d0e, + 0x16e8f9ce, + 0x1772b10c, + 0x142d1c7, + 0x142d1c9, + 0x14bd543, + 0x17f6ae8c, + 0x186e7b09, + 0x18ef8849, + 0x1974a209, + 0x2482, + 0xbeb51, + 0x3711, + 0x248d, + 0x145c51, + 0x8f911, + 0x12b04f, + 0x16adcf, + 0xe7a4c, + 0xf878c, + 0x14a14c, + 0x1dc00d, + 0x1015d5, + 0x661cc, + 0x7394c, + 0x1bad50, + 0x130d4c, + 0x133fcc, + 0x155a59, + 0x162919, + 0x199999, + 0x1b67d4, + 0x1c41d4, + 0x1d09d4, + 0x5a54, + 0x6b54, + 0x19e66289, + 0x1a5d0c89, + 0x1ae73a09, + 0x152e40c9, + 0x2482, + 0x15ae40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x162e40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x16ae40c9, + 0x2482, + 0x172e40c9, + 0x2482, + 0x17ae40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x182e40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x18ae40c9, + 0x2482, + 0x192e40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x19ae40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x1a2e40c9, + 0x2482, + 0x1aae40c9, + 0x2482, + 0x1b2e40c9, + 0x2482, + 0x5a4a, + 0x2482, + 0x1400401, + 0x8f05, + 0x1a3f44, + 0x1442303, + 0x15b08c3, + 0x14ef043, + 0xbec0e, + 0x37ce, + 0x7984e, + 0x254a, + 0x145d0e, + 0x8f9ce, + 0x12b10c, + 0x16ae8c, + 0xe7b09, + 0xf8849, + 0x14a209, + 0x66289, + 0x1d0c89, + 0x73a09, + 0x1017cd, + 0x5d09, + 0x6e09, + 0x14c004, + 0x1a5104, + 0x1b3644, + 0x1b5344, + 0xa4dc4, + 0x1a82c4, + 0x35e04, + 0x50fc4, + 0x13584, + 0x1587c03, + 0xbe9c7, + 0x33d8c, + 0x13583, + 0x9802, + 0x10bb86, + 0x1b3183, + 0x13583, + 0x9bb83, + 0x3f02, + 0x3f08, + 0xdff47, + 0x12ea07, + 0x81c2, 0x2000c2, - 0x203102, + 0x20c302, 0x201d02, - 0x2143c2, + 0x20c202, 0x200382, 0x2003c2, - 0x205fc2, - 0x22f743, - 0x234e83, - 0x224943, - 0x214343, - 0x20ce83, - 0x23f7c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x20ce83, - 0x23f7c3, - 0xb9c3, - 0x224943, - 0x21b84, + 0x204e82, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x214383, + 0x20a803, + 0x216603, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x20a803, + 0x216603, + 0x8503, + 0x228b03, + 0x24e44, 0x2000c2, - 0x24af03, - 0x2022f743, - 0x38fa87, - 0x224943, - 0x20e943, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x21d64a, - 0x22a7c5, - 0x214e03, - 0x21a602, - 0xa7c88, - 0xa7c88, - 0x3102, - 0x1379c2, - 0x20b67d8b, - 0x20e2f9c4, - 0x5d605, - 0xe745, - 0x100a86, - 0x2120e745, - 0x54f03, - 0x1668c3, + 0x248343, + 0x2122c0c3, + 0x38c507, + 0x228b03, + 0x20e403, + 0x217b84, + 0x20a803, + 0x216603, + 0x21de8a, + 0x240985, + 0x216103, + 0x219f82, + 0x9fe08, + 0x9fe08, + 0xc302, + 0x135f02, + 0x21bb990b, + 0x21e2c344, + 0x13e4c5, + 0x7d45, + 0xfdc46, + 0x22207d45, + 0x50cc3, + 0x93003, 0x9c4, 0x11c3, - 0x1273c5, - 0x129245, - 0xa7c88, - 0x1b587, - 0x2f743, - 0x21a3a407, + 0x2b05, + 0x12eb85, + 0x9fe08, + 0x1ae07, + 0x2c0c3, + 0x2c28d, + 0x22a380c7, 0x15c6, - 0x21d6cb05, - 0x3a507, - 0xac8a, - 0xab48, - 0xcb47, - 0x6624a, - 0x18e448, - 0x64bc7, - 0x1a63cf, - 0x3f147, - 0x4dd06, - 0x13f6d0, - 0x7338f, - 0x19209, - 0x58ec4, - 0x2203a5ce, - 0x3ad49, - 0x681c6, - 0x102ec9, - 0x190b46, - 0x1a8ac6, - 0x18d40c, - 0x13a2ca, - 0x35487, - 0x12184a, - 0x176749, - 0xe938c, - 0x1b920a, - 0x8970a, - 0x127409, - 0x58e46, - 0x3554a, - 0x10bb0a, - 0x9fcca, - 0x112349, - 0xdb6c8, - 0xdb946, - 0xe0fcd, - 0xbaf85, - 0x2275308c, - 0x14bf47, - 0x1015c9, - 0xb3207, - 0x103e14, - 0x10430b, - 0x39c0a, - 0x17be0a, - 0xa5a0d, - 0x1513289, - 0x10a9cc, - 0x10b28b, - 0xfcc3, - 0xfcc3, - 0x25786, - 0xfcc3, - 0x100a88, - 0x156b03, - 0x35685, - 0x1412703, - 0x52449, - 0x14cc0c3, - 0x146dbc7, - 0x74cc7, - 0x235c7a89, - 0xcd46, - 0x174f49, - 0x4af03, - 0xa7c88, - 0x3102, - 0x4f044, - 0x7b83, - 0x7ac5, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x21c183, - 0x22f743, - 0x234e83, - 0x20d343, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x2981c3, - 0x2050c3, - 0x21c183, - 0x250784, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x209983, - 0x251c7b85, - 0x142da83, - 0x22f743, - 0x234e83, - 0x2143c3, - 0x20d343, - 0x224943, - 0x221b84, + 0x22ce7945, + 0x1a9012, + 0x381c7, + 0xa0ca, + 0x9f88, + 0xe207, + 0x63d4a, + 0x18aec8, + 0x62607, + 0x18098f, + 0x43dc7, + 0x50dc6, + 0x13b0d0, + 0xc9fcf, + 0x20f09, + 0x56304, + 0x2303828e, + 0x11fb09, + 0x65dc6, + 0x109649, + 0x18ce06, + 0x1ba306, + 0x189e8c, + 0x11424a, + 0x33247, + 0x129b8a, + 0x143889, + 0xed38c, + 0x1ceb0a, + 0x7e54a, + 0x2b49, + 0x56286, + 0x3330a, + 0x11268a, + 0xa0d4a, + 0x127149, + 0xdad88, + 0xdb006, + 0xe208d, + 0xb9545, + 0x23754d4c, + 0x14a647, + 0x107849, + 0xa8c07, + 0x10a594, + 0x10aa8b, + 0x6744a, + 0xfe80a, + 0xa6d8d, + 0x1519b89, + 0x11154c, + 0x111e0b, + 0xe683, + 0xe683, + 0x39346, + 0xe683, + 0xfdc48, + 0x1581c3, + 0x33445, + 0x141d703, + 0x4e9c9, + 0x156d603, + 0x1439887, + 0x80787, + 0x245846c9, + 0xa6c6, + 0xbd389, + 0x48343, + 0x9fe08, + 0xc302, + 0x4b304, + 0x4243, + 0x4185, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x225843, + 0x22c0c3, + 0x232c43, + 0x212483, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x297403, + 0x205b03, + 0x225843, + 0x242244, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x21d0c3, + 0x261847c5, + 0x142bd43, + 0x22c0c3, + 0x232c43, + 0x214403, + 0x212483, + 0x228b03, + 0x224e44, 0x201143, - 0x225a83, - 0x211d83, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x214e03, - 0x25e1e383, - 0x146149, - 0x3102, - 0x3a8583, - 0x26a2f743, - 0x234e83, - 0x249843, - 0x224943, - 0x215cc3, - 0x225a83, - 0x23f7c3, - 0x2f16c3, - 0x39cb04, - 0xa7c88, - 0x2722f743, - 0x234e83, - 0x2ac383, - 0x224943, - 0x211d83, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x230e03, - 0xa7c88, - 0x27a2f743, - 0x234e83, - 0x20d343, - 0x204783, - 0x23f7c3, - 0xa7c88, - 0x1430787, - 0x24af03, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x129245, - 0xe747, - 0x10404b, - 0xdd8c4, - 0xbaf85, - 0x14441c8, - 0x1e00d, - 0x28e6fcc5, - 0x81b84, - 0x3102, - 0xd103, - 0x17ee85, - 0x28002, + 0x238cc3, + 0x211543, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x216103, + 0x26e20643, + 0x147849, + 0xc302, + 0x2ff843, + 0x27a2c0c3, + 0x232c43, + 0x244743, + 0x228b03, + 0x217203, + 0x238cc3, + 0x216603, + 0x2f5cc3, + 0x3a4e04, + 0x9fe08, + 0x2822c0c3, + 0x232c43, + 0x2ac183, + 0x228b03, + 0x211543, + 0x217b84, + 0x20a803, + 0x216603, + 0x22d843, + 0x9fe08, + 0x28a2c0c3, + 0x232c43, + 0x212483, + 0x20ca43, + 0x216603, + 0x9fe08, + 0x142d1c7, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x217b84, + 0x20a803, + 0x216603, + 0x12eb85, + 0x176c87, + 0x10a7cb, + 0xdc144, + 0xb9545, + 0x14470c8, + 0x2a20d, + 0x29e352c5, + 0x46e44, + 0xc302, + 0x8303, + 0x176a85, + 0x2d7c2, 0x1dc2, - 0x326705, - 0xa7c88, - 0xfcc2, - 0x2a83, - 0x16264f, - 0x3102, - 0xf7806, - 0x28002, - 0x3248c8, - 0x250784, - 0x348806, - 0x34b906, - 0xa7c88, - 0x31a3c3, - 0x2c5c09, - 0x359a95, - 0x159a9f, - 0x22f743, - 0x3c2d52, - 0x172886, - 0x1825c5, - 0xc38a, - 0x29889, - 0x3c2b0f, - 0x2dbb04, - 0x36b785, - 0x304a10, - 0x245d47, - 0x204783, - 0x314d88, - 0x1324c6, - 0x29248a, - 0x203b04, - 0x2f0fc3, - 0x21a602, - 0x2eaa8b, - 0x4783, - 0x19fbc4, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x2f8f83, - 0x203102, - 0x1a95c3, - 0x6d84, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x20e943, - 0x225283, - 0x23f7c3, - 0x4af03, - 0x203102, - 0x22f743, - 0x234e83, - 0x20ce83, - 0x4783, - 0x23f7c3, + 0x2e7585, + 0x9fe08, + 0x7e42, + 0xeec3, + 0x16368f, + 0xc302, + 0xfcac6, 0x2000c2, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0xe745, - 0x250784, - 0x22f743, - 0x234e83, - 0x346484, - 0x20ce83, - 0x23f7c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x144a49, - 0x22f743, - 0x234e83, - 0x20d343, - 0x2083c3, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x342644, - 0x221b84, - 0x20ce83, - 0x23f7c3, - 0x2050c3, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x203843, - 0x45e83, - 0xe943, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x31ac4a, - 0x346cc9, - 0x35db4b, - 0x35e24a, - 0x36630a, - 0x37a0cb, - 0x39090a, - 0x395f8a, - 0x39c18a, - 0x39c40b, - 0x3bcf09, - 0x3c8bca, - 0x3c910b, - 0x3d588b, - 0x3da58a, - 0x22f743, - 0x234e83, - 0x20d343, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x1872cb, - 0x5c848, - 0x14c944, - 0x3cb06, - 0xfda89, - 0xa7c88, - 0x22f743, - 0xc384, - 0x263004, - 0x213482, - 0x209c04, - 0x327905, - 0x21c183, - 0x250784, - 0x22f743, - 0x2375c4, - 0x234e83, - 0x24f044, - 0x2dbb04, - 0x221b84, - 0x225a83, - 0x20ce83, - 0x23f7c3, - 0x266f85, - 0x209983, - 0x214e03, - 0x252c43, - 0x23bb84, - 0x376104, - 0x375205, - 0xa7c88, - 0x331c04, - 0x3a8c46, - 0x327544, - 0x203102, - 0x346647, - 0x247947, - 0x24bd44, - 0x25a1c5, - 0x3cd145, - 0x230145, - 0x221b84, - 0x38c348, - 0x22a1c6, - 0x34de48, - 0x27bc45, - 0x2e1985, - 0x265e44, - 0x23f7c3, - 0x2f2484, - 0x378f06, - 0x22a8c3, - 0x23bb84, - 0x234545, + 0x248343, + 0x22c0c3, + 0x228b03, + 0x224e44, + 0x211543, + 0x217b84, + 0x20a803, + 0x216603, + 0x216103, + 0x2d7c2, + 0x32d6c8, + 0x242244, + 0x342bc6, + 0x34a006, + 0x9fe08, + 0x31f443, + 0x20f509, + 0x3046d5, + 0x1046df, + 0x22c0c3, + 0x11d07, + 0x330912, + 0x170a46, + 0x17f0c5, + 0xda4a, + 0x1d3049, + 0x3306cf, + 0x2db1c4, + 0x28ed85, + 0x30b4d0, + 0x322e47, + 0x20ca43, + 0x31b008, + 0xf6f86, + 0x280aca, + 0x224d04, + 0x2f55c3, + 0x219f82, + 0x2eec0b, + 0xca43, + 0x17d404, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, + 0x2fd643, + 0x20c302, + 0x12aec3, + 0x12a8c4, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20e403, + 0x22b0c3, + 0x216603, + 0x48343, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x20a803, + 0xca43, + 0x216603, + 0x2000c2, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x7d45, + 0x242244, + 0x22c0c3, + 0x232c43, + 0x303f84, + 0x20a803, + 0x216603, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x133d89, + 0x22c0c3, + 0x232c43, + 0x212483, + 0x204f43, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x329144, + 0x224e44, + 0x20a803, + 0x216603, + 0x205b03, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x220383, + 0x63f43, + 0xe403, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x32370a, + 0x3484c9, + 0x35f2cb, + 0x35f9ca, + 0x366b8a, + 0x378e4b, + 0x38cbca, + 0x39278a, + 0x39914a, + 0x3993cb, + 0x3b8049, + 0x3c840a, + 0x3c894b, + 0x3d5fcb, + 0x3dc90a, + 0x22c0c3, + 0x232c43, + 0x212483, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, + 0x920b, + 0x5a448, + 0x17acc4, + 0x91c6, + 0x3e109, + 0x9fe08, + 0x22c0c3, + 0xda44, + 0x260904, + 0x20aa02, + 0x217b84, + 0x203045, + 0x225843, + 0x242244, + 0x22c0c3, + 0x235604, + 0x232c43, + 0x24b304, + 0x2db1c4, + 0x224e44, + 0x238cc3, + 0x20a803, + 0x216603, + 0x25cf45, + 0x21d0c3, + 0x216103, + 0x24f1c3, + 0x246484, + 0x2be544, + 0x2bd645, + 0x9fe08, + 0x344b04, + 0x3ba486, + 0x202c84, + 0x20c302, + 0x347d47, + 0x243647, + 0x249204, + 0x238a05, + 0x3cc645, + 0x22cd85, + 0x224e44, + 0x388dc8, + 0x3db706, + 0x32bec8, + 0x27c8c5, + 0x2e2a45, + 0x340644, + 0x216603, + 0x2f61c4, + 0x377bc6, + 0x240a83, + 0x246484, + 0x2411c5, 0x201a84, - 0x248244, - 0x21a602, - 0x337e86, - 0x3b01c6, - 0x308005, + 0x336544, + 0x219f82, + 0x322706, + 0x3ab5c6, + 0x30df85, 0x2000c2, - 0x24af03, - 0x31203102, - 0x21eec4, + 0x248343, + 0x3260c302, + 0x3d0584, 0x200382, - 0x211d83, - 0x23c9c2, - 0x20ce83, + 0x211543, + 0x209082, + 0x20a803, 0x2003c2, - 0x2edc06, - 0x202443, - 0x2050c3, - 0xa7c88, - 0xa7c88, - 0x224943, - 0x71003, + 0x2f1d06, + 0x20c603, + 0x205b03, + 0x9fe08, + 0x9fe08, + 0x228b03, + 0x6d9c3, 0x2000c2, - 0x31e03102, - 0x224943, - 0x264983, + 0x3320c302, + 0x228b03, + 0x2623c3, 0x201143, - 0x22f9c4, - 0x20ce83, - 0x23f7c3, - 0xa7c88, + 0x22c344, + 0x20a803, + 0x216603, + 0x9fe08, 0x2000c2, - 0x32603102, - 0x22f743, - 0x20ce83, - 0x4783, - 0x23f7c3, + 0x33a0c302, + 0x22c0c3, + 0x20a803, + 0xca43, + 0x216603, 0x682, - 0x205842, - 0x210782, - 0x20e943, - 0x2e9343, + 0x206902, + 0x20ff42, + 0x20e403, + 0x2ed343, 0x2000c2, - 0x129245, - 0xa7c88, - 0xe747, - 0x203102, - 0x234e83, - 0x24f044, + 0x12eb85, + 0x9fe08, + 0x176c87, + 0x20c302, + 0x232c43, + 0x24b304, 0x2020c3, - 0x224943, - 0x2083c3, - 0x211d83, - 0x20ce83, - 0x2130c3, - 0x23f7c3, - 0x22a803, - 0x11f293, - 0x125d94, - 0x129245, - 0xe747, - 0x109706, - 0x113ccb, - 0x25786, - 0x56387, - 0x5a1c6, + 0x228b03, + 0x204f43, + 0x211543, + 0x20a803, + 0x213dc3, + 0x216603, + 0x233c43, + 0x1b8a13, + 0x127c94, + 0x12eb85, + 0x176c87, + 0x10f246, + 0x11b7cb, + 0x39346, + 0x531c7, + 0x38a06, 0x649, - 0xd39ca, - 0x8ab4d, - 0x14f50c, - 0x10c48a, - 0xf3dc8, - 0x1339c5, - 0xacc8, - 0x1c6b46, - 0x1c0ec6, - 0xfd986, + 0x160d4a, + 0x8984d, + 0x1263cc, + 0x11300a, + 0x45c88, + 0x175305, + 0xa108, + 0x13686, + 0x1be2c6, + 0x3e006, 0x602, - 0x269102, - 0x4284, - 0x90f46, - 0x187010, - 0x82e4e, - 0x2906, - 0x186dcc, - 0x33e5570b, - 0x129245, - 0x14420b, - 0x343c0e04, - 0x19c847, - 0x21fd1, - 0xfe30a, - 0x22f743, - 0x661c5, - 0x12a3c8, - 0x12984, - 0x586c5, - 0x3448e546, - 0xa6406, - 0xc1886, - 0x9224a, - 0x1a8383, - 0x34a436c4, - 0x52449, - 0x14dfc7, - 0x161ca, - 0x14cd7c9, + 0x209802, + 0x3b04, + 0x9bb86, + 0x184410, + 0x8170e, + 0x2846, + 0x1841cc, + 0x3537314b, + 0x12eb85, + 0x1426cb, + 0x357be204, + 0x1a4107, + 0x25191, + 0x11f54a, + 0x22c0c3, + 0x63cc5, + 0x1bd708, + 0x12704, + 0x55b05, + 0x3588c906, + 0xa7786, + 0xc14c6, + 0x9174a, + 0x1a8883, + 0x35e0bfc4, + 0x4e9c9, + 0x12c047, + 0x1770a, + 0x14ce3c9, 0x605, - 0xfd743, - 0x34f39847, - 0x7085, - 0x1563286, - 0xad04c, - 0xf4f88, - 0xeaccb, - 0x10f9cb, - 0x35249bcc, - 0x1405a83, - 0xbc288, - 0xeaf45, - 0xa1e09, - 0x113388, - 0x141d346, - 0x891c7, - 0x35780589, - 0xc6787, - 0x17894a, - 0x10af4d, - 0x7608, - 0x31643, - 0x1756c3, - 0x100a88, - 0xfe484, - 0x120b85, - 0xe3387, - 0x35a3c8c3, - 0x35f54206, - 0x362f2d04, - 0x366fca47, - 0x100a84, - 0x100a84, - 0x100a84, - 0x100a84, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, + 0xeac83, + 0x3622eb47, + 0x12abc5, + 0x153c6c6, + 0x153f846, + 0xace4c, + 0xfa308, + 0x3643d183, + 0xeee4b, + 0x118a4b, + 0x36a4528c, + 0x14070c3, + 0xbb048, + 0xef0c5, + 0xa2349, + 0x119c88, + 0x141db86, + 0x88147, + 0x36f5b149, + 0x117787, + 0x16074a, + 0x111acd, + 0x8148, + 0x13583, + 0xbdb03, + 0xfdc48, + 0x13584, + 0x1224c5, + 0x148d103, + 0xe85c7, + 0x372e85c3, + 0x377afc46, + 0x37af67c4, + 0x37f02f87, + 0xfdc44, + 0xfdc44, + 0xfdc44, + 0xfdc44, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, 0x2000c2, - 0x203102, - 0x224943, - 0x203842, - 0x20ce83, - 0x23f7c3, - 0x202443, - 0x382e0f, - 0x3831ce, - 0xa7c88, - 0x22f743, - 0x447c7, - 0x234e83, - 0x224943, - 0x214503, - 0x20ce83, - 0x23f7c3, - 0x2844, + 0x20c302, + 0x228b03, + 0x203dc2, + 0x20a803, + 0x216603, + 0x20c603, + 0x37fe4f, + 0x38020e, + 0x9fe08, + 0x22c0c3, + 0x41487, + 0x232c43, + 0x228b03, + 0x214543, + 0x20a803, + 0x216603, + 0x2784, 0x1304, - 0x131544, - 0x21c743, - 0x332907, - 0x202e82, - 0x2c9089, + 0x144444, + 0x21c0c3, + 0x374247, + 0x206082, + 0x26c3c9, 0x200ac2, - 0x24ce8b, - 0x29d7ca, - 0x268f09, + 0x24c0cb, + 0x29d88a, + 0x29e589, 0x200542, - 0x3be4c6, - 0x33ad55, - 0x24cfd5, - 0x3ad113, - 0x24d553, - 0x2197c2, - 0x21dbc5, - 0x32f1cc, - 0x2779cb, - 0x298645, - 0x202602, - 0x2fae02, - 0x393ac6, - 0x203002, - 0x37de46, - 0x20c60d, - 0x206e0c, - 0x224084, + 0x212ac6, + 0x38d7d5, + 0x24c215, + 0x231e53, + 0x24c793, + 0x212082, + 0x21e405, + 0x369e0c, + 0x27494b, + 0x297885, + 0x202542, + 0x299082, + 0x37bf46, + 0x202282, + 0x37bb46, + 0x20dccd, + 0x32a94c, + 0x226604, 0x200882, - 0x206c82, - 0x3396c8, + 0x20be02, + 0x22e9c8, 0x200202, - 0x305bc6, - 0x305bcf, - 0x397e50, - 0x3a3c04, - 0x33af15, - 0x3ad293, - 0x209843, - 0x34850a, - 0x206207, - 0x36a889, - 0x216087, - 0x21c342, + 0x30e386, + 0x30e38f, + 0x393f90, + 0x21f204, + 0x38d995, + 0x231fd3, + 0x21cf83, + 0x349d0a, + 0x208b87, + 0x369349, + 0x2175c7, + 0x227682, 0x200282, - 0x3b6906, - 0x204382, - 0xa7c88, - 0x208d42, - 0x209002, - 0x209007, - 0x268d07, - 0x268d11, - 0x217fc5, - 0x217fce, - 0x21a94f, - 0x21c782, - 0x398507, - 0x21cd88, - 0x205142, - 0x2bfa42, - 0x20f7c6, - 0x20f7cf, - 0x281750, - 0x22c542, - 0x207982, - 0x32b408, - 0x207983, - 0x25ed88, - 0x2e1d0d, - 0x280c03, - 0x34b748, - 0x280c0f, - 0x280fce, - 0x37baca, - 0x223111, - 0x223590, - 0x2c7bcd, - 0x2c7f0c, - 0x3a8407, - 0x348687, - 0x3488c9, - 0x21c302, + 0x3b4e46, + 0x203c02, + 0x9fe08, + 0x208082, + 0x208342, + 0x21e9c7, + 0x330307, + 0x330311, + 0x218585, + 0x21858e, + 0x21968f, + 0x209582, + 0x373907, + 0x21c708, + 0x205b82, + 0x2c1142, + 0x21bb46, + 0x21bb4f, + 0x269f10, + 0x22a8c2, + 0x204042, + 0x251c88, + 0x204043, + 0x25c6c8, + 0x2db50d, + 0x2094c3, + 0x3bc3c8, + 0x27f40f, + 0x27f7ce, + 0x2fe4ca, + 0x2da0d1, + 0x2da550, + 0x2dc90d, + 0x2dcc4c, + 0x2ff6c7, + 0x349e87, + 0x342c89, + 0x226702, 0x201ec2, - 0x257f8c, - 0x25828b, + 0x2553cc, + 0x2556cb, 0x200d42, - 0x2c2946, - 0x227f02, + 0x2c4946, + 0x202c82, 0x200482, - 0x2291c2, - 0x203102, - 0x22fb44, - 0x23a0c7, - 0x22ca82, - 0x23ffc7, - 0x241a47, - 0x227d02, - 0x231082, - 0x244685, - 0x224502, - 0x2e738e, - 0x38564d, - 0x234e83, - 0x28814e, - 0x3d1a0d, - 0x348cc3, + 0x28cac2, + 0x20c302, + 0x22c784, + 0x237d87, + 0x22ae02, + 0x23cbc7, + 0x23ed47, + 0x22e002, + 0x22dac2, + 0x241345, + 0x259dc2, + 0x382a4e, + 0x3c91cd, + 0x232c43, + 0x28658e, + 0x3d234d, + 0x32fe43, 0x202102, - 0x286204, - 0x24ab42, - 0x2072c2, - 0x39ee05, - 0x3a0887, - 0x2491c2, - 0x2143c2, - 0x24ec47, - 0x2527c8, - 0x2b1d82, - 0x27cf06, - 0x257e0c, - 0x25814b, - 0x208142, - 0x25f7cf, - 0x25fb90, - 0x25ff8f, - 0x260355, - 0x260894, - 0x260d8e, - 0x26110e, - 0x26148f, - 0x26184e, - 0x261bd4, - 0x2620d3, - 0x26258d, - 0x279509, - 0x28ce83, + 0x284744, + 0x24a542, + 0x2253c2, + 0x39b645, + 0x39ce07, + 0x243f82, + 0x20c202, + 0x24af07, + 0x24ed48, + 0x2b12c2, + 0x278086, + 0x25524c, + 0x25558b, + 0x203d42, + 0x25d0cf, + 0x25d490, + 0x25d88f, + 0x25dc55, + 0x25e194, + 0x25e68e, + 0x25ea0e, + 0x25ed8f, + 0x25f14e, + 0x25f4d4, + 0x25f9d3, + 0x25fe8d, + 0x276a09, + 0x28b243, 0x2020c2, - 0x35ac05, - 0x3ce486, + 0x35c2c5, + 0x3cfd46, 0x200382, - 0x2b5787, - 0x224943, + 0x3776c7, + 0x228b03, 0x200642, - 0x233b88, - 0x223351, - 0x223790, - 0x203702, - 0x28b387, + 0x231448, + 0x2da311, + 0x2da750, + 0x202b82, + 0x28a387, 0x201742, - 0x2a1687, - 0x251b42, - 0x3bde09, - 0x393a87, - 0x293348, - 0x28e386, - 0x2e9243, - 0x349345, - 0x212702, + 0x247f07, + 0x24e082, + 0x328c49, + 0x37bf07, + 0x296c48, + 0x28c746, + 0x28f303, + 0x28f305, + 0x21d702, 0x2004c2, - 0x3d7045, - 0x386b45, + 0x3b5245, + 0x383f45, 0x201b02, - 0x22cb43, - 0x344587, - 0x20ea47, + 0x22aec3, + 0x342a47, + 0x20c907, 0x201f82, 0x201f84, - 0x20fb43, - 0x2e64c9, - 0x20fb48, - 0x20e942, - 0x205542, - 0x2dcec7, - 0x2fa285, - 0x3635c8, - 0x227807, - 0x222303, - 0x290dc6, - 0x2c7a4d, - 0x2c7dcc, - 0x2d8a06, - 0x204482, - 0x2030c2, - 0x205982, - 0x280a8f, - 0x280e8e, - 0x3cd1c7, - 0x204802, - 0x31c445, - 0x31c446, - 0x21f382, + 0x20e503, + 0x2ebf89, + 0x20e508, + 0x203102, + 0x205f82, + 0x2eb887, + 0x3dd285, + 0x33fb88, + 0x34e347, + 0x21a7c3, + 0x29ba06, + 0x2dc78d, + 0x2dcb0c, + 0x2d5d46, + 0x204b02, + 0x21fc02, + 0x206a42, + 0x27f28f, + 0x27f68e, + 0x3cc6c7, + 0x206702, + 0x295d05, + 0x295d06, + 0x21d902, 0x200bc2, - 0x28fc06, - 0x20a643, - 0x20a646, - 0x2cb845, - 0x2cb84d, - 0x2cc255, - 0x2ccc8c, - 0x2cd00d, - 0x2cd352, - 0x204fc2, - 0x26ff42, - 0x202142, - 0x2fbc86, - 0x3c1e06, + 0x28d746, + 0x2062c3, + 0x206586, + 0x2cc185, + 0x2cc18d, + 0x2cc7d5, + 0x2cd88c, + 0x2cdc0d, + 0x2cdf52, + 0x205a02, + 0x26bf42, + 0x2050c2, + 0x3dbec6, + 0x3b0d86, 0x202f42, - 0x3ce506, - 0x218782, - 0x333105, - 0x2090c2, - 0x2e74c9, - 0x21980c, - 0x219b4b, + 0x3cfdc6, + 0x218d42, + 0x374a45, + 0x202f82, + 0x382b89, + 0x22330c, + 0x22364b, 0x2003c2, - 0x253188, + 0x24f708, 0x201902, 0x200a82, - 0x274246, - 0x26f545, + 0x271f86, + 0x2e4045, 0x200a87, - 0x222d85, - 0x277485, - 0x23cb02, - 0x20ad82, - 0x204ac2, - 0x2e5647, - 0x2edccd, - 0x2ee04c, - 0x3ac4c7, - 0x27ce82, + 0x228445, + 0x2563c5, + 0x2091c2, + 0x20a1c2, + 0x2161c2, + 0x3a7c87, + 0x2f1dcd, + 0x2f214c, + 0x243047, + 0x278002, 0x201c82, - 0x20da88, + 0x3c63c8, 0x201c88, - 0x2e2ac8, - 0x2f5c44, - 0x2c35c7, - 0x25d743, - 0x21a142, - 0x203b02, - 0x2ef7c9, - 0x225b87, - 0x214e02, - 0x274645, - 0x203542, - 0x228742, - 0x30b703, - 0x30b706, - 0x2f8c82, - 0x2faa02, + 0x32c1c8, + 0x2faf84, + 0x2c5807, + 0x33e603, + 0x223c42, + 0x212842, + 0x2f3749, + 0x26aec7, + 0x216102, + 0x272385, + 0x220082, + 0x20b182, + 0x2fd083, + 0x2fd086, + 0x2fd202, + 0x301b02, 0x200402, - 0x278f46, - 0x2e2087, - 0x214c02, + 0x36c686, + 0x2aba07, + 0x215f02, 0x200902, - 0x25ebcf, - 0x287f8d, - 0x39bb4e, - 0x3d188c, - 0x204542, - 0x2023c2, - 0x28e1c5, - 0x319386, + 0x25c50f, + 0x2863cd, + 0x3b12ce, + 0x3d21cc, + 0x204bc2, + 0x203d82, + 0x28c585, + 0x320886, 0x200b82, - 0x2046c2, + 0x204d42, 0x200682, - 0x227784, - 0x2e1b84, - 0x3580c6, - 0x205fc2, - 0x27c847, - 0x23df83, - 0x23df88, - 0x23e6c8, - 0x3729c7, - 0x24fb06, + 0x286744, + 0x2db384, + 0x359786, + 0x204e82, + 0x286e87, + 0x23bc03, + 0x23bc08, + 0x23f748, + 0x37f1c7, + 0x24e306, 0x201702, - 0x238f43, - 0x2ab4c7, - 0x30d6c6, - 0x2e4f85, - 0x2f5fc8, - 0x2026c2, - 0x3be287, - 0x234542, - 0x354282, - 0x205c42, - 0x21aac9, + 0x2183c3, + 0x2183c7, + 0x314946, + 0x2e7e85, + 0x2fb308, + 0x202602, + 0x3a9687, + 0x2411c2, + 0x293042, + 0x209442, + 0x219809, 0x201082, - 0xbe948, - 0x202a02, - 0x24d983, - 0x203c47, - 0x205202, - 0x21998c, - 0x219c8b, - 0x2d8a86, - 0x2fd685, - 0x244d02, + 0xc41c8, + 0x2021c2, + 0x2432c3, + 0x202247, + 0x205c42, + 0x22348c, + 0x22378b, + 0x2d5dc6, + 0x2eabc5, + 0x247c82, 0x201942, - 0x2bdbc6, - 0x229043, - 0x3423c7, - 0x282482, + 0x2bf146, + 0x236483, + 0x328ec7, + 0x235282, 0x2008c2, - 0x33abd5, - 0x24d195, - 0x3acfd3, - 0x24d6d3, - 0x248f47, - 0x259d91, - 0x25e050, - 0x2652d2, - 0x275191, - 0x277e08, - 0x277e10, - 0x29784f, - 0x29d593, - 0x2a6812, - 0x2a7e50, - 0x30014f, - 0x373352, - 0x3bc7d1, - 0x2b3813, - 0x2c0892, - 0x3a22cf, - 0x2cb4ce, - 0x2d47d2, - 0x2d51d1, - 0x2d94cf, - 0x2da2ce, - 0x2de751, - 0x2dfa90, - 0x2f8812, - 0x2eb451, - 0x2f5150, - 0x2f900f, - 0x2fc451, - 0x3018d0, - 0x35efc6, - 0x3cbf07, - 0x2137c7, + 0x38d655, + 0x24c3d5, + 0x231d13, + 0x24c913, + 0x37f647, + 0x25b951, + 0x262d10, + 0x274d92, + 0x2779d1, + 0x284bc8, + 0x284bd0, + 0x2d7c8f, + 0x29d653, + 0x29e352, + 0x29ffd0, + 0x2a7b8f, + 0x2a9e12, + 0x305811, + 0x371353, + 0x3b78d2, + 0x2b2e8f, + 0x2cbe0e, + 0x2cd412, + 0x2d3c51, + 0x2d430f, + 0x2d830e, + 0x2d9791, + 0x2de010, + 0x2df0d2, + 0x2e8a91, + 0x2ef5d0, + 0x2fa4cf, + 0x2fd6d1, + 0x3029d0, + 0x31bb46, + 0x3adfc7, + 0x20ce47, 0x201a42, - 0x283c05, - 0x304787, - 0x210782, - 0x20e802, - 0x22ab45, - 0x21e5c3, - 0x375b86, - 0x2ede8d, - 0x2ee1cc, - 0x2038c2, - 0x32f04b, - 0x27788a, - 0x21da8a, - 0x2bd149, - 0x2ecb4b, - 0x22794d, - 0x304e8c, - 0x27680a, - 0x272f8c, - 0x2788cb, - 0x29848c, - 0x2ef24e, - 0x2b458b, - 0x36e20c, - 0x2e1643, - 0x34c706, - 0x3c0dc2, - 0x2f6602, - 0x206603, - 0x205d42, - 0x21ed83, - 0x323386, - 0x260507, - 0x2d2b06, - 0x2e3c88, - 0x344408, - 0x315a46, + 0x2824c5, + 0x30b247, + 0x20ff42, + 0x207e02, + 0x229545, + 0x220883, + 0x2bdfc6, + 0x2f1f8d, + 0x2f22cc, + 0x217042, + 0x369c8b, + 0x27480a, + 0x21e2ca, + 0x2bc0c9, + 0x2f0c0b, + 0x34e48d, + 0x30b94c, + 0x25b3ca, + 0x27108c, + 0x2758cb, + 0x2976cc, + 0x31ce0e, + 0x36710b, + 0x2b1d4c, + 0x2e2703, + 0x37aa86, + 0x3bcb02, + 0x2fbb02, + 0x25a083, + 0x20ff82, + 0x233b03, + 0x324b86, + 0x25de07, + 0x2e0e06, + 0x2e1e88, + 0x3428c8, + 0x31d5c6, 0x200f02, - 0x3079cd, - 0x307d0c, - 0x30fb87, - 0x30c2c7, - 0x236942, - 0x215002, - 0x277dc2, - 0x27b2c2, - 0x336d56, - 0x33ead5, - 0x341c16, - 0x344f93, - 0x345652, - 0x356693, - 0x356dd2, - 0x3af0cf, - 0x3bf998, - 0x3c0897, - 0x3c13d9, - 0x3c2258, - 0x3c3698, - 0x3c47d7, - 0x3c5b17, - 0x3c70d6, - 0x3cad53, - 0x3cb695, - 0x3cc2d2, - 0x3cc753, - 0x203102, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x202443, + 0x30d94d, + 0x30dc8c, + 0x318c07, + 0x312e47, + 0x229942, + 0x216302, + 0x218342, + 0x279642, + 0x335056, + 0x33a4d5, + 0x33d6d6, + 0x346693, + 0x346d52, + 0x357d53, + 0x358492, + 0x3aa4cf, + 0x3bbb18, + 0x3bc5d7, + 0x3bdc19, + 0x3be7d8, + 0x3bf698, + 0x3c46d7, + 0x3c57d7, + 0x3c7016, + 0x3ca6d3, + 0x3cbc95, + 0x3cc992, + 0x3cce13, + 0x20c302, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x217b84, + 0x20a803, + 0x216603, + 0x20c603, 0x2000c2, - 0x203782, - 0x386948c5, - 0x38a89f05, - 0x38e78c06, - 0xa7c88, - 0x392b3e85, - 0x203102, + 0x203042, + 0x39e946c5, + 0x3a219305, + 0x3a675c06, + 0x9fe08, + 0x3aab3405, + 0x20c302, 0x201d02, - 0x3972c885, - 0x39a82105, - 0x39e83387, - 0x3a284089, - 0x3a638644, + 0x3aefedc5, + 0x3b280405, + 0x3b681c47, + 0x3ba82949, + 0x3bf064c4, 0x200382, 0x200642, - 0x3aa5f145, - 0x3ae99b89, - 0x3b336408, - 0x3b6b0985, - 0x3bb0ce07, - 0x3be20688, - 0x3c2e7805, - 0x3c63c586, - 0x3ca47a89, - 0x3cedb288, - 0x3d2c3188, - 0x3d69a1ca, - 0x3dba9704, - 0x3de08705, - 0x3e2bf288, - 0x3e601885, - 0x2131c2, - 0x3ea32003, - 0x3eea5146, - 0x3f26d148, - 0x3f602e46, - 0x3fa09148, - 0x3ff26406, - 0x40241f04, - 0x40608ac2, - 0x40fdb307, - 0x412acd84, - 0x4167b947, - 0x41b2b747, + 0x3c25aec5, + 0x3c6998c9, + 0x3cb34708, + 0x3ceafc45, + 0x3d313987, + 0x3d622508, + 0x3db11985, + 0x3de9a406, + 0x3e243789, + 0x3e6d1dc8, + 0x3eac45c8, + 0x3ee99f0a, + 0x3f277244, + 0x3f605285, + 0x3fac0b08, + 0x3fe01885, + 0x213ec2, + 0x40242a83, + 0x406a64c6, + 0x40b4f408, + 0x40e1f986, + 0x412dd348, + 0x41779b06, + 0x41a3f204, + 0x41e05642, + 0x427315c7, + 0x42aacb84, + 0x42e79cc7, + 0x433c4d87, 0x2003c2, - 0x41e9e485, - 0x42245a84, - 0x426d7fc7, - 0x42a31607, - 0x42e86046, - 0x43282bc5, - 0x43699c87, - 0x43adb108, - 0x43f2bac7, - 0x442b3489, - 0x446d4505, - 0x44b08a87, - 0x44e93f06, - 0x81b8b, - 0x45209d88, - 0x22034d, - 0x28bf09, - 0x2a8e4b, - 0x2ac48b, - 0x30f00b, - 0x30cb0b, - 0x31958b, - 0x31984b, - 0x319d89, - 0x31aecb, - 0x31b18b, - 0x31b70b, - 0x31ce0a, - 0x31d34a, - 0x31d94c, - 0x322b0b, - 0x3235ca, - 0x34340a, - 0x34cc8e, - 0x34d88e, - 0x34dc0a, - 0x34ffca, - 0x3509cb, - 0x350c8b, - 0x35190b, - 0x36ed0b, - 0x36f30a, - 0x36ffcb, - 0x37028a, - 0x37050a, - 0x37078a, - 0x3919cb, - 0x396f4b, - 0x39984e, - 0x399bcb, - 0x3a0a4b, - 0x3a19cb, - 0x3a540a, - 0x3a5689, - 0x3a58ca, - 0x3a75ca, - 0x3bf38b, - 0x3c93cb, - 0x3c9c8a, - 0x3ca78b, - 0x3d2f4b, - 0x3d9fcb, - 0x456849c8, - 0x45a8a2c9, - 0x45ea1c89, - 0x462e2608, - 0x357e45, - 0x209ec3, - 0x23d284, - 0x2c86c5, - 0x238386, - 0x23c305, - 0x289984, - 0x2b5688, - 0x312ec5, - 0x296184, - 0x3dc887, - 0x2a04ca, - 0x384bca, - 0x3cd2c7, - 0x218747, - 0x2decc7, - 0x27e007, - 0x35cf45, - 0x3cf6c6, - 0x33a747, - 0x32a7c4, - 0x2b6486, - 0x2ed886, - 0x3ce8c5, - 0x329d04, - 0x29b206, - 0x29f587, - 0x26c806, - 0x3daf07, - 0x27f503, - 0x3d3206, - 0x233105, - 0x283487, - 0x269c8a, - 0x233c84, - 0x218f88, - 0x346a89, - 0x2d2347, - 0x397806, - 0x36b848, - 0x203589, - 0x36aa44, - 0x35af84, - 0x2d5fc5, - 0x22af48, - 0x2c9ac7, - 0x2f7149, - 0x224d08, - 0x30e406, - 0x23d386, - 0x29bb48, - 0x373ac6, - 0x289f05, - 0x286106, - 0x27c1c8, - 0x280986, - 0x2405cb, - 0x366786, - 0x29d10d, - 0x3dc385, - 0x2acc46, - 0x202c45, - 0x367649, - 0x24c047, - 0x39cf08, - 0x292fc6, - 0x29c3c9, - 0x3bd346, - 0x269c05, - 0x2a2fc6, - 0x2bd946, - 0x2ce4c9, - 0x2bb546, - 0x2a01c7, - 0x2a3645, - 0x21a1c3, - 0x21a1c5, - 0x2ad647, - 0x32f846, - 0x3dc289, - 0x278c06, - 0x274886, - 0x3cf389, - 0x285b09, - 0x2a3bc7, - 0x3286c8, - 0x2a6249, - 0x283888, - 0x36eb86, - 0x2db485, - 0x31608a, - 0x274906, - 0x2096c6, - 0x2d2e45, - 0x253e48, - 0x2aa007, - 0x23190a, - 0x24f846, - 0x2f6245, - 0x2facc6, - 0x228987, - 0x3976c7, - 0x21b8c5, - 0x269dc5, - 0x2815c6, - 0x28e686, - 0x2a9cc6, - 0x2bf744, - 0x285089, - 0x28b146, - 0x2cf20a, - 0x21c588, - 0x308788, - 0x384bca, - 0x21b105, - 0x29f4c5, - 0x23b4c8, - 0x2bfc88, - 0x237187, - 0x2b7bc6, - 0x33cd48, - 0x20fec7, - 0x282d08, - 0x2b9e46, - 0x286e88, - 0x2991c6, - 0x27bdc7, - 0x2af486, - 0x29b206, - 0x26e40a, - 0x2d8b86, - 0x2db489, - 0x369786, - 0x2684ca, - 0x241f09, - 0x2ee686, - 0x2bbd04, - 0x35accd, - 0x28a547, - 0x32c386, - 0x2c3045, - 0x3bd3c5, - 0x393146, - 0x2d7e09, - 0x2ba7c7, - 0x27d386, - 0x2d0d06, - 0x289a09, - 0x289e44, - 0x242a84, - 0x327f08, - 0x265d46, - 0x2a30c8, - 0x2f7f88, - 0x32e307, - 0x3bb0c9, - 0x3b8c47, - 0x2b3d4a, - 0x2f028f, - 0x281e8a, - 0x28dfc5, - 0x27c405, - 0x2140c5, - 0x3ba6c7, - 0x228543, - 0x3288c8, - 0x25ce46, - 0x25cf49, - 0x2dbdc6, - 0x2ce307, - 0x29c189, - 0x39ce08, - 0x2d2f07, - 0x316fc3, - 0x357ec5, - 0x2284c5, - 0x2bf58b, - 0x201944, - 0x302084, - 0x27a646, - 0x317187, - 0x39e38a, - 0x232087, - 0x3a8647, - 0x282105, - 0x3c2845, - 0x2725c9, - 0x29b206, - 0x231f0d, - 0x363505, - 0x2b6e03, - 0x204943, - 0x21ef45, - 0x35cbc5, - 0x36b848, - 0x27dcc7, - 0x242806, - 0x2a1906, - 0x22b6c5, - 0x235147, - 0x32de07, - 0x22a087, - 0x20878a, - 0x3d32c8, - 0x2bf744, - 0x280707, - 0x280447, - 0x350f06, - 0x298847, - 0x2d1348, - 0x2d3b88, - 0x24bf46, - 0x218988, - 0x2bb5c4, - 0x33a746, - 0x253a86, - 0x39d606, - 0x330946, - 0x21af84, - 0x27e0c6, - 0x2c1a86, - 0x29b5c6, - 0x231f06, - 0x204806, - 0x2457c6, - 0x242708, - 0x2b6308, - 0x2d72c8, - 0x23c508, - 0x23b446, - 0x20b3c5, - 0x21a186, - 0x2b0a05, - 0x395647, - 0x224dc5, - 0x20d443, - 0x3ceac5, - 0x22e384, - 0x204945, - 0x201903, - 0x3a3787, - 0x3436c8, - 0x3dafc6, - 0x2b4ecd, - 0x27c3c6, - 0x29ab85, - 0x21aac3, - 0x2bec49, - 0x289fc6, - 0x2969c6, - 0x282604, - 0x281e07, - 0x338446, - 0x2baa85, - 0x23c043, - 0x205b44, - 0x280606, - 0x3cf7c4, - 0x253b88, - 0x3d4a09, - 0x302a89, - 0x2a2eca, - 0x29370d, - 0x234007, - 0x3a8986, - 0x20adc4, - 0x284089, - 0x288c08, - 0x28a146, - 0x236d86, - 0x298847, - 0x2c1606, - 0x22d906, - 0x32ca06, - 0x32b7ca, - 0x220688, - 0x26ed05, - 0x36c209, - 0x2ca24a, - 0x2ffbc8, - 0x29ecc8, - 0x296948, - 0x2ad80c, - 0x351e45, - 0x2a1b88, - 0x2b7946, - 0x310e06, - 0x3a1c47, - 0x231f85, - 0x286285, - 0x302949, - 0x20d207, - 0x25cf05, - 0x238cc7, - 0x204943, - 0x2ca705, - 0x21a748, - 0x284e07, - 0x29eb89, - 0x2e2e05, - 0x3a6744, - 0x2a4448, - 0x3db447, - 0x2d30c8, - 0x3dd108, - 0x2adbc5, - 0x3baa86, - 0x24c3c6, - 0x2d6389, - 0x31c0c7, - 0x2b0e46, - 0x21ea07, - 0x202103, - 0x238644, - 0x2cf8c5, - 0x235284, - 0x2515c4, - 0x38c647, - 0x2675c7, - 0x27d544, - 0x29e9d0, - 0x36c407, - 0x3c2845, - 0x3303cc, - 0x206784, - 0x37f5c8, - 0x27bcc9, - 0x388d86, - 0x3134c8, - 0x23d044, - 0x27a948, - 0x3dbb06, - 0x26e288, - 0x29fb46, - 0x28ae8b, - 0x325045, - 0x2cf748, - 0x20f1c4, - 0x3d4e4a, - 0x29eb89, - 0x2af386, - 0x30e988, - 0x286685, - 0x2be1c4, - 0x37f4c6, - 0x229f48, - 0x2849c8, - 0x33d5c6, - 0x321104, - 0x316006, - 0x3b8cc7, - 0x27b847, - 0x29884f, - 0x329587, - 0x2ee747, - 0x31c305, - 0x377fc5, - 0x2a3889, - 0x2e6c46, - 0x38c885, - 0x285e07, - 0x3a1ec8, - 0x3cf845, - 0x2af486, - 0x21c3c8, - 0x202e4a, - 0x229c48, - 0x28f987, - 0x2f06c6, - 0x36c1c6, - 0x2003c3, - 0x2060c3, - 0x2ca409, - 0x2a60c9, - 0x2b3386, - 0x2e2e05, - 0x218c08, - 0x30e988, - 0x373c48, - 0x32ca8b, - 0x2b5107, - 0x314bc9, - 0x298ac8, - 0x35bb44, - 0x39d248, - 0x291489, - 0x2b1145, - 0x3ba5c7, - 0x2386c5, - 0x2848c8, - 0x29474b, - 0x2999d0, - 0x2ac885, - 0x20f10c, - 0x2429c5, - 0x282183, - 0x2ef046, - 0x2c0d04, - 0x2af746, - 0x29f587, - 0x21c444, - 0x243108, - 0x32878d, - 0x30e845, - 0x234044, - 0x294e44, - 0x294e49, - 0x2c4008, - 0x325507, - 0x3dbb88, - 0x285148, - 0x27d685, - 0x211907, - 0x27d607, - 0x2c59c7, - 0x269dc9, - 0x32d909, - 0x20bdc6, - 0x2c8106, - 0x285ec6, - 0x34d0c5, - 0x3adbc4, - 0x3bff46, - 0x3c19c6, - 0x27d6c8, - 0x22864b, - 0x2658c7, - 0x20adc4, - 0x338386, - 0x2d1687, - 0x2453c5, - 0x333385, - 0x227044, - 0x32d886, - 0x3bffc8, - 0x284089, - 0x247086, - 0x288a08, - 0x2bab46, - 0x35c1c8, - 0x2b8c0c, - 0x27d546, - 0x29a84d, - 0x29accb, - 0x2a0285, - 0x32df47, - 0x2bb646, - 0x397588, - 0x20be49, - 0x3b2508, - 0x3c2845, - 0x32a507, - 0x283988, - 0x232b49, - 0x3380c6, - 0x27f74a, - 0x397308, - 0x3b234b, - 0x22090c, - 0x27aa48, + 0x4369e985, + 0x43a88bc4, + 0x43ed0e07, + 0x4423b747, + 0x44684586, + 0x44a81485, + 0x44e999c7, + 0x452d1c48, + 0x457c5107, + 0x45b48289, + 0x45ed3985, + 0x463102c7, + 0x46693d06, + 0x46e4b, + 0x46a7b288, + 0x2221cd, + 0x27d049, + 0x28858b, + 0x2aa48b, + 0x2b734b, + 0x31368b, + 0x320a8b, + 0x320d4b, + 0x321b89, + 0x32398b, + 0x323c4b, + 0x32430b, + 0x32548a, + 0x3259ca, + 0x325fcc, + 0x32bc4b, + 0x32c3ca, + 0x3419ca, + 0x34d0ce, + 0x35024e, + 0x3505ca, + 0x3521ca, + 0x352bcb, + 0x352e8b, + 0x353b0b, + 0x36d78b, + 0x36dd8a, + 0x36ea4b, + 0x36ed0a, + 0x36ef8a, + 0x36f20a, + 0x38decb, + 0x39360b, + 0x395ace, + 0x395e4b, + 0x39cfcb, + 0x39de8b, + 0x3a124a, + 0x3a14c9, + 0x3a170a, + 0x3a31ca, + 0x3bb50b, + 0x3c8c0b, + 0x3c960a, + 0x3ca10b, + 0x3d38cb, + 0x3dc34b, + 0x46e82f08, + 0x47288fc9, + 0x476a21c9, + 0x47ae4d08, + 0x359505, + 0x218e03, + 0x27e244, + 0x2abc05, + 0x306206, + 0x34d545, + 0x288844, + 0x3775c8, + 0x3197c5, + 0x295604, + 0x3c17c7, + 0x2a154a, + 0x381fca, + 0x3cc7c7, + 0x218d07, + 0x2de547, + 0x282bc7, + 0x35e6c5, + 0x3b0106, + 0x3b0347, + 0x3bdb04, + 0x2f1246, + 0x2f1146, + 0x3b06c5, + 0x355784, + 0x29af46, + 0x2a0607, + 0x26a746, + 0x31f207, + 0x27e303, + 0x39c446, + 0x251ec5, + 0x281d47, + 0x267aca, + 0x231544, + 0x220c88, + 0x310809, + 0x2cc587, + 0x38f346, + 0x28ee48, + 0x2200c9, + 0x369504, + 0x35c644, + 0x2d5085, + 0x225d48, + 0x2ca407, + 0x2f3249, + 0x228ec8, + 0x315686, + 0x229a46, + 0x29b888, + 0x371b06, + 0x219305, + 0x284646, + 0x27a648, + 0x27f186, + 0x25440b, + 0x292ec6, + 0x29d1cd, + 0x3c12c5, + 0x2aca46, + 0x210985, + 0x3b91c9, + 0x249507, + 0x3a4a48, + 0x314486, + 0x29c489, + 0x3b8486, + 0x267a45, + 0x215146, + 0x2c9006, + 0x2cee49, + 0x2b9b06, + 0x2a1247, + 0x2a4385, + 0x207683, + 0x223cc5, + 0x2affc7, + 0x36a486, + 0x3c11c9, + 0x275c06, + 0x279ec6, + 0x21a549, + 0x284049, + 0x2a4907, + 0x344e08, + 0x2a75c9, + 0x282148, + 0x3929c6, + 0x2dab45, + 0x278eca, + 0x279f46, + 0x21ce06, + 0x2d28c5, + 0x24ff08, + 0x2eb407, + 0x22f24a, + 0x24bb06, + 0x2f4745, + 0x302086, + 0x328687, + 0x38f207, + 0x21b145, + 0x267c05, + 0x269d86, + 0x26e4c6, 0x27fdc6, - 0x211308, - 0x202ac7, - 0x2092c9, - 0x30cf4d, - 0x29b106, - 0x239808, - 0x2b61c9, - 0x2bf848, - 0x286f88, - 0x2c24cc, - 0x2c3787, - 0x2c44c7, - 0x269c05, - 0x2b91c7, - 0x3a1d88, - 0x37f546, - 0x246f0c, - 0x2f4408, - 0x2d0648, - 0x232e46, - 0x228247, - 0x20bfc4, - 0x23c508, - 0x315b4c, - 0x28844c, - 0x28e045, - 0x3ce947, - 0x321086, - 0x2281c6, - 0x367808, - 0x21d004, - 0x26c80b, - 0x27c98b, - 0x2f06c6, - 0x328607, - 0x331e05, - 0x273d05, - 0x26c946, - 0x286645, - 0x201905, - 0x2ccac7, - 0x3c6349, - 0x28e844, - 0x25b245, - 0x30b645, - 0x3bae48, - 0x28d605, - 0x2a0ec9, - 0x2e8247, - 0x2e824b, - 0x2ee3c6, - 0x242449, - 0x329c48, - 0x2919c5, - 0x2c5ac8, - 0x32d948, - 0x264587, - 0x3db907, - 0x38c6c9, - 0x26e1c7, - 0x29cec9, - 0x308fcc, - 0x2b3388, - 0x2bb089, - 0x2bc447, - 0x285209, - 0x23bec7, - 0x220a08, - 0x202a05, - 0x33a6c6, - 0x2c3088, - 0x2f07c8, - 0x2ca109, - 0x201947, - 0x274705, - 0x248809, - 0x2d66c6, - 0x293f04, - 0x35f406, - 0x26cfc8, - 0x2fa847, - 0x228848, - 0x218a49, - 0x32b087, - 0x2a0686, - 0x32e004, - 0x3ceb49, - 0x211788, - 0x232d07, - 0x22e7c6, - 0x228586, - 0x209644, - 0x36d946, - 0x2048c3, - 0x324bc9, - 0x325006, - 0x2acec5, - 0x2a1906, - 0x2ce885, - 0x283e08, - 0x369b87, - 0x2feac6, - 0x32c8c6, - 0x308788, - 0x2a3a07, - 0x29b145, - 0x29e7c8, - 0x3c97c8, - 0x397308, - 0x242885, - 0x33a746, - 0x302849, - 0x2d6204, - 0x2ce70b, - 0x22d60b, - 0x26ec09, - 0x204943, - 0x258c45, - 0x23d606, - 0x242dc8, - 0x2aae04, - 0x3dafc6, - 0x2088c9, - 0x2d0445, - 0x2cca06, - 0x3db446, - 0x214184, - 0x29ee4a, - 0x2ace08, - 0x2f07c6, - 0x245205, - 0x328487, - 0x35ce07, - 0x3baa84, - 0x22d847, - 0x224d84, - 0x224d86, - 0x210143, - 0x269dc5, - 0x2b1b05, - 0x369dc8, - 0x2808c5, - 0x27d289, - 0x23c347, - 0x23c34b, - 0x2a4f4c, - 0x2a554a, - 0x30ce07, - 0x202e03, - 0x38cd48, - 0x242a45, - 0x3cf8c5, - 0x357f84, - 0x220906, - 0x27bcc6, - 0x36d987, - 0x247d8b, - 0x21af84, - 0x2fd104, - 0x2b52c4, - 0x2ce186, - 0x21c444, - 0x22b048, - 0x357d85, - 0x21b745, - 0x373b87, - 0x32e049, - 0x35cbc5, - 0x39314a, - 0x2a3549, - 0x2afb0a, - 0x32b909, - 0x350204, - 0x2d0dc5, - 0x2c1708, - 0x2d808b, - 0x2d5fc5, - 0x2f8106, - 0x241b04, - 0x27d7c6, - 0x32af09, - 0x2d1787, - 0x278dc8, - 0x293a86, - 0x3b8c47, - 0x2849c8, - 0x3936c6, - 0x3c1c04, - 0x383507, - 0x371cc5, - 0x385207, - 0x23c584, - 0x2bb5c6, - 0x2ff8c8, - 0x29ae88, - 0x2f1d87, - 0x320dc8, - 0x299285, - 0x204784, - 0x384ac8, - 0x31c544, - 0x214045, - 0x2ffac4, - 0x20ffc7, - 0x28b207, - 0x285348, - 0x2d3246, - 0x280845, - 0x27d088, - 0x2516c8, - 0x2a2e09, - 0x22d906, - 0x231988, - 0x3d4cca, - 0x245448, - 0x2e7805, - 0x21a386, - 0x2a3408, - 0x32a5ca, - 0x2aaa07, - 0x289045, - 0x294108, - 0x3d2484, - 0x253ec6, - 0x2c4848, - 0x204806, - 0x3ca308, - 0x358587, - 0x3dc786, - 0x2bbd04, - 0x26c187, - 0x2b6784, - 0x32aec7, - 0x2af0cd, - 0x237205, - 0x2d7c0b, - 0x2886c6, - 0x253288, - 0x2430c4, - 0x23b646, - 0x280606, - 0x211647, - 0x29a50d, - 0x2f6a87, - 0x2b6d48, - 0x284245, - 0x37b488, - 0x2c9a46, - 0x299308, - 0x360146, - 0x330147, - 0x2856c9, - 0x35fac7, - 0x28a408, - 0x276cc5, - 0x22b748, - 0x228105, - 0x225d05, - 0x34b085, - 0x24f543, - 0x286184, - 0x245505, - 0x247a89, - 0x36d746, - 0x2d1448, - 0x3db6c5, - 0x2b9087, - 0x31098a, - 0x2cc949, - 0x2bd84a, - 0x2d7348, - 0x238b0c, - 0x285e8d, - 0x3cc203, - 0x3ca208, - 0x205b05, - 0x202c06, - 0x39cc86, - 0x35a985, - 0x21eb09, - 0x36b3c5, - 0x27d088, - 0x2574c6, - 0x35e9c6, - 0x2a4309, - 0x3ab3c7, - 0x294a06, - 0x310908, - 0x39d508, - 0x2e2807, - 0x2c1c0e, - 0x2c9c85, - 0x232a45, - 0x204708, - 0x2e42c7, - 0x200e42, - 0x2c2044, - 0x2af64a, - 0x232dc8, - 0x32da86, - 0x29c2c8, - 0x24c3c6, - 0x32f488, - 0x2b0e48, - 0x225cc4, - 0x2b9445, - 0x727544, - 0x727544, - 0x727544, - 0x208343, - 0x228406, - 0x27d546, - 0x29ff4c, - 0x204743, - 0x23cf46, - 0x21c4c4, - 0x289f48, - 0x208705, - 0x2af746, - 0x2bf388, - 0x2d9206, - 0x2fea46, - 0x3ae0c8, - 0x2cf947, - 0x26df89, - 0x31a70a, - 0x208744, - 0x224dc5, - 0x23d345, - 0x358206, - 0x234046, - 0x2a0a86, - 0x3cce06, - 0x26e0c4, - 0x26e0cb, - 0x224b84, - 0x2425c5, - 0x2affc5, - 0x32e3c6, - 0x204c88, - 0x285d47, - 0x324f84, - 0x25c6c3, - 0x3d1f85, - 0x35f2c7, - 0x285c4b, - 0x369cc7, - 0x2bf288, - 0x2b9587, - 0x26b786, - 0x28c1c8, - 0x272b0b, - 0x2c8606, - 0x20df49, - 0x272c85, - 0x316fc3, - 0x2cca06, - 0x358488, - 0x213603, - 0x28c7c3, - 0x209e86, - 0x24c3c6, - 0x379a8a, - 0x27fe05, - 0x28044b, - 0x2a184b, - 0x246183, - 0x20a043, - 0x2b3cc4, - 0x24c187, - 0x27aa44, - 0x289f44, - 0x2b77c4, - 0x245748, - 0x245148, - 0x206049, - 0x2d4588, - 0x2686c7, - 0x231f06, - 0x2d108f, - 0x2c9dc6, - 0x2d6b04, - 0x244f8a, - 0x35f1c7, - 0x2b6886, - 0x293f49, - 0x205fc5, - 0x369f05, - 0x206106, - 0x22b883, - 0x3d24c9, - 0x220806, - 0x218809, - 0x39e386, - 0x269dc5, - 0x28e445, - 0x20a703, - 0x24c2c8, - 0x3256c7, - 0x25ce44, - 0x289dc8, - 0x310b84, - 0x300f46, - 0x2ef046, - 0x23ebc6, - 0x2cf609, - 0x3cf845, - 0x29b206, - 0x2a1509, - 0x2c8886, - 0x2457c6, - 0x3a3b86, - 0x203685, - 0x2ffac6, - 0x330144, - 0x202a05, - 0x2c3084, - 0x2b76c6, - 0x3634c4, - 0x201a43, - 0x288cc5, - 0x235e48, - 0x22cdc7, - 0x2aae89, - 0x288f48, - 0x29b911, - 0x3db4ca, - 0x2f0607, - 0x2d3ec6, - 0x21c4c4, - 0x2c3188, - 0x3cf988, - 0x29baca, - 0x2a0c8d, - 0x2a2fc6, - 0x3ae1c6, - 0x26c246, - 0x21b747, - 0x2b6e05, - 0x3be587, - 0x289e85, - 0x2e8384, - 0x2adec6, - 0x33a587, - 0x3d21cd, - 0x2a3347, - 0x2b5588, - 0x27d389, - 0x21a286, - 0x338045, - 0x230344, - 0x26d0c6, - 0x3ba986, - 0x232f46, - 0x29cb48, - 0x219783, - 0x209e43, - 0x320405, - 0x35b006, - 0x2b0e05, - 0x293c88, - 0x29f74a, - 0x3bab84, - 0x289f48, - 0x296948, - 0x32e207, - 0x3db789, - 0x2bef88, - 0x284107, - 0x2b7a46, - 0x20480a, - 0x26d148, - 0x390e89, - 0x2c40c8, - 0x226c49, - 0x2d3d87, - 0x2f3bc5, - 0x32cc86, - 0x37f3c8, - 0x253408, - 0x339cc8, - 0x214188, - 0x2425c5, - 0x200d04, - 0x2347c8, - 0x241884, - 0x32b704, - 0x269dc5, - 0x2961c7, - 0x32de09, - 0x211447, - 0x2319c5, - 0x27a846, - 0x369406, - 0x202fc4, - 0x2a4646, - 0x27f244, - 0x292b46, - 0x32dbc6, - 0x213446, - 0x3c2845, - 0x293b47, - 0x202e03, - 0x269889, - 0x308588, - 0x283f84, - 0x283f8d, - 0x29af88, - 0x2ec008, - 0x390e06, - 0x2857c9, - 0x2cc949, - 0x32ac05, - 0x29f84a, - 0x25364a, - 0x27108c, - 0x271206, - 0x27b146, - 0x2ca646, - 0x39eec9, - 0x202e46, - 0x220a86, - 0x36b486, - 0x23c508, - 0x229c46, - 0x2d5acb, - 0x296345, - 0x21b745, - 0x27b945, - 0x327c86, - 0x2047c3, - 0x23eb46, - 0x2a32c7, - 0x2c3045, - 0x25ab85, - 0x3bd3c5, - 0x312906, - 0x32acc4, - 0x336306, - 0x2abc89, - 0x327b0c, - 0x2e80c8, - 0x229ec4, - 0x2ff7c6, - 0x2887c6, - 0x358488, - 0x30e988, - 0x327a09, - 0x328487, - 0x265a89, - 0x273dc6, - 0x22c644, - 0x205584, - 0x283704, - 0x2849c8, - 0x32dc4a, - 0x35cb46, - 0x36d607, - 0x385487, - 0x242545, - 0x2f70c4, - 0x291446, - 0x2b6e46, - 0x202a83, - 0x3083c7, - 0x3dd008, - 0x32ad4a, - 0x23d9c8, - 0x209148, - 0x363505, - 0x2a0385, - 0x2659c5, - 0x242906, - 0x390046, - 0x37b985, - 0x324e09, - 0x2f6ecc, - 0x380907, - 0x29bb48, - 0x296685, - 0x727544, - 0x2270c4, - 0x284f44, - 0x218606, - 0x2a268e, - 0x369f87, - 0x21b945, - 0x2d618c, - 0x3cb3c7, - 0x33a507, - 0x358fc9, - 0x219049, - 0x289045, - 0x308588, - 0x302849, - 0x3971c5, - 0x2c2f88, - 0x2bb2c6, - 0x384d46, - 0x241f04, - 0x290108, - 0x21a443, - 0x387344, - 0x3d2005, - 0x398bc7, - 0x22c405, - 0x3d4b89, - 0x2a4c0d, - 0x371086, - 0x3b9904, - 0x2b7b48, - 0x3c618a, - 0x221e07, - 0x326945, - 0x280883, - 0x2a1a0e, - 0x24c3cc, - 0x2ffcc7, - 0x2a2847, - 0x40b06207, - 0x1282c6, - 0x81b84, - 0x202e83, - 0x202e85, - 0x284f45, - 0x29c688, - 0x29a009, - 0x229dc6, - 0x27aa44, - 0x2f0546, - 0x2395cb, - 0x2c77cc, - 0x24f607, - 0x2d5d85, - 0x3c96c8, - 0x2e25c5, - 0x244f87, - 0x3db307, - 0x339b45, - 0x2047c3, - 0x210584, - 0x23d245, - 0x28e745, - 0x28e746, - 0x2a8a48, - 0x33a587, - 0x39cf86, - 0x209546, - 0x34afc6, - 0x239989, - 0x211a07, - 0x27f346, - 0x2c7946, - 0x3a9606, - 0x2acd45, - 0x208bc6, - 0x386605, - 0x28d688, - 0x295b4b, - 0x290cc6, - 0x3854c4, - 0x2d87c9, - 0x23c344, - 0x2bb248, - 0x35f507, - 0x286e84, - 0x2be3c8, - 0x2c42c4, - 0x2acd84, - 0x289d05, - 0x30e886, - 0x245687, - 0x2172c3, - 0x2a0745, - 0x273304, - 0x232a86, - 0x32ac88, - 0x320cc5, - 0x295809, - 0x248a05, - 0x23cf48, - 0x3d28c7, - 0x325108, - 0x2be007, - 0x2ee809, - 0x27df46, - 0x370c46, - 0x2a6384, - 0x2fd045, - 0x30724c, - 0x27b947, - 0x27c2c7, - 0x233c88, - 0x371086, - 0x2a3204, - 0x3415c4, - 0x38c549, - 0x2ca746, - 0x272647, - 0x211284, - 0x2a4746, - 0x37adc5, - 0x2d2d87, - 0x2d5a46, - 0x27f609, - 0x2e6e47, - 0x298847, - 0x2a4186, - 0x22e705, - 0x282b88, - 0x220688, - 0x2dccc6, - 0x320d05, - 0x2c5406, - 0x2017c3, - 0x29c509, - 0x2a080e, - 0x2bdd48, - 0x310c88, - 0x2dcacb, - 0x295a46, - 0x326404, - 0x285a84, - 0x2a090a, - 0x20f007, - 0x27f405, - 0x20df49, - 0x2c1b45, - 0x32b747, - 0x2329c4, - 0x297307, - 0x2f7e88, - 0x2d2406, - 0x2bb749, - 0x2bf08a, - 0x20ef86, - 0x29aac6, - 0x2aff45, - 0x39a185, - 0x31fa87, - 0x246d08, - 0x37ad08, - 0x225cc6, - 0x28e4c5, - 0x233dce, - 0x2bf744, - 0x29c605, - 0x27a1c9, - 0x2e6a48, - 0x28f8c6, - 0x29e2cc, - 0x29f350, - 0x2a22cf, - 0x2a3788, - 0x30ce07, - 0x3c2845, - 0x245505, - 0x245509, - 0x294309, - 0x316106, - 0x2d6047, - 0x2fcf45, - 0x237189, - 0x350f86, - 0x202c8d, + 0x226384, 0x2835c9, - 0x289f44, - 0x2bdac8, - 0x234889, - 0x35cd06, - 0x38cf45, - 0x370c46, - 0x278c89, - 0x212348, - 0x20b3c5, - 0x290104, - 0x29e48b, - 0x35cbc5, - 0x242e46, - 0x2861c6, - 0x32eb86, - 0x29524b, - 0x295909, - 0x209485, - 0x395547, - 0x3db446, - 0x3ae2c6, - 0x284cc8, - 0x22b1c9, - 0x2b534c, - 0x35f0c8, - 0x3118c6, - 0x33d5c3, - 0x2ff4c6, - 0x295085, - 0x281348, - 0x28dec6, - 0x2d2fc8, - 0x232105, - 0x29bc85, - 0x3d2a08, - 0x39d3c7, - 0x39cbc7, - 0x36d987, - 0x3134c8, - 0x358308, - 0x2d1bc6, - 0x2b7507, - 0x238507, - 0x294f4a, - 0x2292c3, - 0x327c86, - 0x22a005, - 0x245a84, - 0x27d389, - 0x2ee784, - 0x203c44, - 0x29fbc4, - 0x2a284b, - 0x325607, - 0x234005, - 0x298f88, - 0x27a846, - 0x27a848, - 0x27fd46, - 0x290045, - 0x290305, - 0x2920c6, - 0x293548, - 0x293e88, - 0x27d546, - 0x298dcf, - 0x29bfd0, - 0x3dc385, - 0x202e03, - 0x22c705, - 0x314b08, - 0x294209, - 0x397308, - 0x2d5e48, - 0x23aac8, - 0x3256c7, - 0x27a509, - 0x2d31c8, - 0x291204, - 0x29fa48, - 0x3baf09, - 0x2b87c7, - 0x2ad784, - 0x211508, - 0x29390a, - 0x2ddc86, - 0x2a2fc6, - 0x22d7c9, - 0x29f587, - 0x2cf488, - 0x2e21c8, - 0x209c08, - 0x249085, - 0x39af85, - 0x21b745, - 0x284f05, - 0x2b6007, - 0x2047c5, - 0x2c3045, - 0x2389c6, - 0x397247, - 0x2d7fc7, - 0x293c06, - 0x2d7885, - 0x242e46, - 0x2b9605, - 0x2bfb08, - 0x2fcec4, - 0x2c8906, - 0x323484, - 0x2be1c8, - 0x2c8a0a, - 0x27dccc, - 0x247f85, - 0x21b806, - 0x2b5506, - 0x3202c6, - 0x311944, - 0x39ca85, - 0x27ef87, - 0x29f609, - 0x2ce5c7, - 0x727544, - 0x727544, - 0x325485, - 0x216144, - 0x29dc8a, - 0x27a6c6, - 0x302644, - 0x3ce8c5, - 0x2b5a05, - 0x2b6d44, - 0x285e07, - 0x248987, - 0x2ce188, - 0x2c5508, - 0x20b3c9, - 0x31c548, - 0x29de4b, - 0x245644, - 0x3ae3c5, - 0x38c905, - 0x36d909, - 0x22b1c9, - 0x2d86c8, - 0x224b88, - 0x2dd504, - 0x288805, - 0x209ec3, - 0x3581c5, - 0x29b286, - 0x299e4c, - 0x211186, - 0x38ce46, - 0x28fb45, - 0x312988, - 0x30eb06, - 0x2d4046, - 0x2a2fc6, - 0x23d74c, - 0x38c9c4, - 0x34b10a, - 0x28fa88, - 0x299c87, - 0x273206, - 0x229e87, - 0x2f0145, - 0x22e7c6, - 0x366086, - 0x377e87, - 0x22ce84, - 0x2100c5, - 0x27a1c4, - 0x2e8407, - 0x27a408, - 0x27afca, - 0x283807, - 0x2ac947, - 0x30cd87, - 0x2e2709, - 0x299e4a, - 0x22c603, - 0x22cd85, - 0x213483, - 0x2b7809, - 0x3586c8, - 0x31c307, - 0x397409, - 0x220786, - 0x3296c8, - 0x3a3705, - 0x2517ca, - 0x328a49, - 0x24be09, - 0x3a1c47, - 0x3cfa89, - 0x213348, - 0x32f686, - 0x21b9c8, - 0x3c3e47, - 0x26e1c7, - 0x2a3547, - 0x2db108, - 0x2ff646, - 0x2936c5, - 0x27ef87, - 0x29a5c8, - 0x34af44, - 0x2cf0c4, - 0x294907, - 0x2b11c7, - 0x3026ca, - 0x32f606, - 0x37b28a, - 0x2c1f87, - 0x2bf507, - 0x210184, - 0x29cf84, - 0x2d2c86, - 0x3386c4, - 0x3386cc, - 0x302585, - 0x213fc9, - 0x23d0c4, - 0x2b6e05, - 0x3c6108, - 0x293f45, - 0x393146, - 0x294444, - 0x2ae3ca, - 0x31bfc6, - 0x24080a, - 0x32bac7, - 0x228985, - 0x22b885, - 0x24258a, - 0x339c05, - 0x2454c6, - 0x241884, - 0x2b3e46, - 0x31fb45, - 0x28df86, - 0x2f1d8c, - 0x2d9bca, - 0x253744, - 0x231f06, - 0x29f587, - 0x2d59c4, - 0x23c508, - 0x2e5046, - 0x385309, - 0x2cbc49, - 0x2b3489, - 0x2ce8c6, - 0x3c3f46, - 0x21bb07, - 0x324d48, - 0x3c3d49, - 0x325607, - 0x299106, - 0x3b8cc7, - 0x26c105, - 0x2bf744, - 0x21b6c7, - 0x2386c5, - 0x289c45, - 0x200cc7, - 0x339a08, - 0x3c9646, - 0x29b40d, - 0x29c88f, - 0x2a184d, - 0x208904, - 0x235f46, - 0x2d9f88, - 0x36b445, - 0x295108, - 0x26444a, - 0x289f44, - 0x2ed986, - 0x2d6b87, - 0x21af87, - 0x2cfa09, - 0x21b985, - 0x2b6d44, - 0x2b938a, - 0x2beb49, - 0x3cfb87, - 0x2eecc6, - 0x35cd06, - 0x288746, - 0x3835c6, - 0x2d988f, - 0x2d9e49, - 0x229c46, - 0x38c186, - 0x324409, - 0x2b7607, - 0x222b43, - 0x23d8c6, - 0x2060c3, - 0x35a848, - 0x2a9d87, - 0x2a3989, - 0x2eeec8, - 0x39cd08, - 0x23c006, - 0x2110c9, - 0x380845, - 0x273204, - 0x2f3c87, - 0x39ef45, - 0x208904, - 0x2340c8, - 0x20f2c4, - 0x2b7347, - 0x343646, - 0x281685, - 0x2c40c8, - 0x35cbcb, - 0x308a87, - 0x242806, - 0x2c9e44, - 0x326386, - 0x269dc5, - 0x2386c5, - 0x282909, - 0x285a09, - 0x26e204, - 0x26e245, - 0x231f45, - 0x251646, - 0x308688, - 0x2c1086, - 0x3dce4b, - 0x388c0a, - 0x2be105, - 0x290386, - 0x25cb45, - 0x2e1fc5, - 0x296ac7, - 0x327f08, - 0x265a84, - 0x264046, - 0x293f06, - 0x213507, - 0x316f84, - 0x280606, - 0x3ba7c5, - 0x3ba7c9, - 0x20d344, - 0x2f7249, - 0x27d546, - 0x2c3848, - 0x231f45, - 0x385585, - 0x28df86, - 0x2b5249, - 0x219049, - 0x38cec6, - 0x2e6b48, - 0x2a4d48, - 0x25cb04, - 0x2b9c44, - 0x2b9c48, - 0x32c488, - 0x265b89, - 0x29b206, - 0x2a2fc6, - 0x33cc0d, - 0x3dafc6, - 0x2b8ac9, - 0x3bb285, - 0x206106, - 0x209d88, - 0x336245, - 0x238544, - 0x269dc5, - 0x285548, - 0x29da49, - 0x27a284, - 0x2bb5c6, - 0x39104a, - 0x2ffbc8, - 0x302849, - 0x26ad8a, - 0x397386, - 0x29ca48, - 0x244d45, - 0x28fd08, - 0x2f01c5, - 0x220649, - 0x33ef89, - 0x210642, - 0x272c85, - 0x273a46, - 0x27d487, - 0x245a85, - 0x2f6146, - 0x30c0c8, - 0x371086, - 0x2c15c9, - 0x27c3c6, - 0x284b48, - 0x38d285, - 0x2fe046, - 0x330248, - 0x2849c8, - 0x2d3c88, - 0x30e488, - 0x208bc4, - 0x23b083, - 0x2c1804, - 0x283a06, - 0x26c144, - 0x310bc7, - 0x2d3f49, - 0x2c8c85, - 0x2e21c6, - 0x23d8c6, - 0x2a888b, - 0x2b67c6, - 0x363746, - 0x2cc7c8, - 0x23d386, - 0x228783, - 0x2077c3, - 0x2bf744, - 0x231885, - 0x2ba987, - 0x27a408, - 0x27a40f, - 0x27ee8b, - 0x308488, - 0x2bb646, - 0x30878e, - 0x232ac3, - 0x2ba904, - 0x2b6745, - 0x2b6bc6, - 0x29154b, - 0x296286, - 0x21c449, - 0x281685, - 0x24fc08, - 0x208208, - 0x218f0c, - 0x2a2886, - 0x358206, - 0x2e2e05, - 0x28a1c8, - 0x27dcc5, - 0x35bb48, - 0x29e64a, - 0x2a1c89, - 0x727544, - 0x2000c2, - 0x46a03102, - 0x200382, - 0x221b84, - 0x205982, - 0x346484, - 0x208ac2, - 0x4783, - 0x2003c2, - 0x202442, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x24af03, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x20ce83, - 0x23f7c3, - 0x232d43, - 0x250784, - 0x22f743, - 0x2375c4, - 0x234e83, - 0x2dbb04, - 0x224943, - 0x245d47, - 0x211d83, - 0x204783, - 0x314d88, - 0x23f7c3, - 0x29248b, - 0x2f0fc3, - 0x22a7c6, - 0x21a602, - 0x2eaa8b, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x23f7c3, - 0x203a83, - 0x207cc3, - 0x2000c2, - 0xa7c88, - 0x217285, - 0x238748, - 0x2f9448, - 0x203102, - 0x342585, - 0x3b8e07, - 0x201bc2, - 0x243307, - 0x200382, - 0x256d07, - 0x375509, - 0x2c93c8, - 0x209a89, - 0x20ba42, - 0x3bb387, - 0x2dc944, - 0x3b8ec7, - 0x388b07, - 0x25bd02, - 0x211d83, - 0x204fc2, - 0x208ac2, - 0x2003c2, - 0x204ac2, - 0x200902, - 0x202442, - 0x20a745, - 0x20f805, - 0x3102, - 0x34e83, - 0x22f743, - 0x234e83, - 0x211343, - 0x224943, - 0x2083c3, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x8d43, - 0x101, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x214503, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x2158c3, - 0x49c27bc6, - 0x3c8c3, - 0xca085, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x39c2, - 0xa7c88, - 0x123fc3, - 0x4783, - 0x71003, - 0x46cc4, - 0x1421004, - 0xe29c5, - 0x2000c2, - 0x395904, - 0x22f743, - 0x234e83, - 0x224943, - 0x250483, - 0x230145, - 0x214503, - 0x20e943, - 0x20ce83, - 0x22c483, - 0x23f7c3, - 0x202443, - 0x250803, - 0x2050c3, - 0x5c2, - 0x28002, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x2000c2, - 0x24af03, - 0x203102, - 0x234e83, - 0x224943, - 0x221b84, - 0x20ce83, - 0x23f7c3, - 0x202442, - 0xa7c88, - 0x224943, - 0x71003, - 0xa7c88, - 0x71003, - 0x271803, - 0x22f743, - 0x232244, - 0x234e83, - 0x224943, - 0x203842, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x203842, - 0x225a83, - 0x20ce83, - 0x23f7c3, - 0x2e9343, - 0x202443, - 0x2000c2, - 0x203102, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x22a7c5, - 0x112406, - 0x250784, - 0x21a602, - 0xa7c88, - 0x2000c2, - 0x129245, - 0x1cb88, - 0x133c43, - 0x203102, - 0x4e495d86, - 0xc284, - 0x10404b, - 0x369c6, - 0x11e87, - 0x234e83, - 0x4a308, - 0x4a30b, - 0x4a78b, - 0x4ac8b, - 0x4afcb, - 0x4b28b, - 0x4b6cb, - 0x1c6986, - 0x224943, - 0x34c5, - 0x128c44, - 0x20ff83, - 0x10e2c7, - 0xdf604, - 0x70144, - 0x20ce83, - 0x18cfc6, - 0xb40c4, - 0x71003, - 0x23f7c3, - 0x2f2484, - 0x125c07, - 0x112009, - 0x103e08, - 0x127304, - 0xfd986, - 0x7608, - 0x68c45, - 0x7909, - 0x31e43, - 0x129245, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x204783, - 0x23f7c3, - 0x2f0fc3, - 0x21a602, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x214343, - 0x209c04, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x2dbb04, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x22a7c6, - 0x234e83, - 0x224943, - 0x3fc83, - 0x71003, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x129245, - 0x11e87, - 0x4b83, - 0x31e43, - 0xa7c88, - 0x224943, - 0x22f743, - 0x234e83, - 0x224943, - 0x5ee03, - 0x20ce83, - 0x23f7c3, - 0x51a2f743, - 0x234e83, - 0x20ce83, - 0x23f7c3, - 0xa7c88, - 0x2000c2, - 0x203102, - 0x22f743, - 0x224943, - 0x20ce83, - 0x2003c2, - 0x23f7c3, - 0x33f4c7, - 0x2c5d4b, - 0x2171c3, - 0x315dc8, - 0x324ac7, - 0x206686, - 0x213a45, - 0x3426c9, - 0x211b08, - 0x37e609, - 0x3a5f50, - 0x37e60b, - 0x2e3949, - 0x204b83, - 0x2eb149, - 0x233646, - 0x23364c, - 0x217348, - 0x3d7e08, - 0x3759c9, - 0x2b828e, - 0x3752cb, - 0x32cecc, - 0x21c183, - 0x28764c, - 0x3c7709, - 0x302187, - 0x234dcc, - 0x2b238a, - 0x23d544, - 0x3b27cd, - 0x287508, - 0x35460d, - 0x30d5c6, - 0x25078b, - 0x312189, - 0x38c407, - 0x2fb986, - 0x3be009, - 0x32a88a, - 0x31a548, - 0x2f0bc4, + 0x28a146, + 0x2fd3ca, + 0x2278c8, + 0x30ffc8, + 0x381fca, + 0x223fc5, + 0x2a0545, + 0x3c1e08, + 0x2bcc08, + 0x266e07, + 0x226b86, + 0x338748, + 0x20e887, + 0x2815c8, + 0x2b8cc6, + 0x285308, + 0x298406, + 0x27ca47, + 0x3229c6, + 0x29af46, + 0x26ed4a, + 0x2d5ec6, + 0x2dab49, + 0x368506, + 0x21eb0a, + 0x23f209, + 0x2f2786, + 0x2baac4, + 0x35c38d, + 0x289247, + 0x2e6186, + 0x2c4485, + 0x3b8505, + 0x38fe46, + 0x2d0c49, + 0x2b7907, + 0x27bb46, + 0x2c9e46, + 0x2888c9, + 0x359a44, + 0x244f84, + 0x340e88, + 0x236846, + 0x2a3e08, + 0x2150c8, + 0x219447, + 0x3b5b89, + 0x27ffc7, + 0x2b32ca, + 0x2f420f, + 0x26f44a, + 0x28c385, + 0x27a885, + 0x214f05, + 0x21f147, + 0x267083, + 0x345008, + 0x20ef06, + 0x20f009, + 0x3cb186, + 0x2d0487, + 0x29c249, + 0x3a4948, + 0x2d2987, + 0x31da43, + 0x359585, + 0x3281c5, + 0x2261cb, + 0x201944, + 0x308804, + 0x278686, + 0x31dc07, + 0x39abca, + 0x242b07, + 0x2ff907, + 0x280405, + 0x3bfc85, + 0x26de89, + 0x29af46, + 0x24298d, + 0x33fac5, + 0x2b6243, + 0x23ffc3, + 0x3d0605, + 0x35e345, + 0x28ee48, + 0x27c487, + 0x244d06, + 0x2a1e46, + 0x229e05, + 0x232f07, + 0x2e6d07, + 0x3db5c7, + 0x20530a, + 0x39c508, + 0x226384, + 0x27ef07, + 0x27ec47, + 0x353106, + 0x297a87, + 0x2e0388, + 0x360f08, + 0x249406, + 0x218f48, + 0x2b9b84, + 0x3b0346, + 0x238786, + 0x3aa006, + 0x345606, + 0x219cc4, + 0x282c86, + 0x2c3106, + 0x29b306, + 0x22f846, + 0x3c6986, + 0x2e01c6, + 0x244c08, + 0x2b4d08, + 0x2d6848, + 0x34d748, + 0x3c1d86, + 0x209a85, + 0x223c86, + 0x2afcc5, 0x391e47, - 0x23b747, - 0x330ac4, - 0x215dc4, - 0x331609, - 0x268309, - 0x28dc48, - 0x2ed2c5, - 0x20b985, - 0x2045c6, - 0x3b2689, - 0x2646cd, - 0x2f8208, - 0x2044c7, - 0x213ac8, - 0x23ae06, - 0x3a1344, - 0x284505, - 0x3c3c46, + 0x228f85, + 0x20ba83, + 0x20ca85, + 0x230fc4, + 0x3c6ac5, + 0x201903, + 0x393907, + 0x36d1c8, + 0x31f2c6, + 0x376e0d, + 0x27a846, + 0x29a8c5, + 0x219803, + 0x2c04c9, + 0x359bc6, + 0x296206, + 0x398b84, + 0x26f3c7, + 0x36c186, + 0x2b7bc5, + 0x242943, + 0x3d7004, + 0x27ee06, + 0x238884, + 0x2e9f88, + 0x3befc9, + 0x309209, + 0x2a3c0a, + 0x2a54cd, + 0x2318c7, + 0x3ba1c6, + 0x20a204, + 0x282949, + 0x287b88, + 0x288e46, + 0x234b46, + 0x297a87, + 0x2c1246, + 0x34fc46, + 0x2ffa86, + 0x3c4e0a, + 0x222508, + 0x2e3805, + 0x33e9c9, + 0x2cab8a, + 0x305288, + 0x29f1c8, + 0x296188, + 0x2e710c, + 0x350805, + 0x2a20c8, + 0x2b5006, + 0x317646, + 0x3d8287, + 0x242a05, + 0x2847c5, + 0x3090c9, + 0x20ac07, + 0x20efc5, + 0x237187, + 0x23ffc3, + 0x2cb045, + 0x21ac08, + 0x283347, + 0x29f089, + 0x2dedc5, + 0x3ae2c4, + 0x2a5188, + 0x331707, + 0x2d2b48, + 0x3d36c8, + 0x2adbc5, + 0x21f506, + 0x249886, + 0x2d5449, + 0x2b3f47, + 0x2b0386, + 0x3d00c7, + 0x205083, + 0x3064c4, + 0x2d8c85, + 0x233044, + 0x248d84, + 0x3890c7, + 0x2651c7, + 0x27bd04, + 0x29eed0, + 0x33ebc7, + 0x3bfc85, + 0x2f764c, + 0x32a2c4, + 0x2b2b48, + 0x27c949, + 0x385146, + 0x319dc8, + 0x270d84, + 0x278988, + 0x331dc6, + 0x26ebc8, + 0x2a0bc6, + 0x2d004b, + 0x32de45, + 0x2d8b08, + 0x213304, + 0x3bf40a, + 0x29f089, + 0x3228c6, + 0x225fc8, + 0x258305, + 0x2bfd44, + 0x2b2a46, + 0x3db488, + 0x282f08, + 0x338fc6, + 0x301104, + 0x278e46, + 0x280047, + 0x279bc7, + 0x297a8f, + 0x32eec7, + 0x2f2847, + 0x295bc5, + 0x376185, + 0x2a45c9, + 0x2d7886, + 0x389305, + 0x284347, + 0x2cd008, + 0x2f9c05, + 0x3229c6, + 0x227708, + 0x21f98a, + 0x3db188, + 0x28d4c7, + 0x2f4646, + 0x33e986, + 0x2003c3, + 0x208a43, + 0x2cad49, + 0x2a7449, + 0x2b2946, + 0x2dedc5, + 0x2191c8, + 0x225fc8, + 0x371c88, + 0x2ffb0b, + 0x377047, + 0x31ae49, + 0x297d08, + 0x351c84, + 0x3a9c48, + 0x290cc9, + 0x2b0685, + 0x21f047, + 0x306545, + 0x282e08, + 0x29454b, + 0x299710, + 0x2ac685, + 0x21324c, + 0x244ec5, + 0x280483, + 0x31cc06, + 0x2c2644, + 0x288cc6, + 0x2a0607, + 0x212bc4, + 0x23ffc8, + 0x344ecd, + 0x31c485, + 0x231904, + 0x2a3484, + 0x2a3489, + 0x2af088, + 0x32e307, + 0x331e48, + 0x283688, + 0x27be45, + 0x2110c7, + 0x27bdc7, + 0x20f2c7, + 0x267c09, + 0x2e6809, + 0x3c3b86, + 0x2dce46, + 0x284406, + 0x323fc5, + 0x3af9c4, + 0x3bcb46, + 0x3bed86, + 0x27be88, + 0x32834b, + 0x2363c7, + 0x20a204, + 0x36c0c6, + 0x2e06c7, + 0x3da1c5, + 0x374cc5, + 0x227c84, + 0x2e6786, + 0x3bcbc8, + 0x282949, + 0x264806, + 0x287988, + 0x2b7c86, + 0x35d948, + 0x32170c, + 0x27bd06, + 0x29a58d, + 0x29aa0b, + 0x2a1305, + 0x2e6e47, + 0x2b9c06, + 0x38f0c8, + 0x3c3c09, + 0x307e48, + 0x3bfc85, + 0x3bd847, + 0x282248, + 0x3c0bc9, + 0x36be06, + 0x26470a, + 0x38ee48, + 0x307c8b, + 0x22278c, + 0x278a88, + 0x27e846, + 0x210ac8, + 0x21f607, + 0x21ca09, + 0x3983cd, + 0x29ae46, + 0x267048, + 0x2b4bc9, + 0x2c0f48, + 0x285408, + 0x2c3b4c, + 0x2c5107, + 0x2c5bc7, + 0x267a45, + 0x2c0d87, + 0x2ccec8, + 0x2b2ac6, + 0x2934cc, + 0x2f9688, + 0x2d1588, + 0x234d86, + 0x34ef07, + 0x3c3d84, + 0x34d748, + 0x28688c, + 0x289b8c, + 0x28c405, + 0x3b0747, + 0x301086, + 0x34ee86, + 0x3b9388, + 0x21c984, + 0x26a74b, + 0x286fcb, + 0x2f4646, + 0x344d47, + 0x28f405, + 0x271a45, + 0x26a886, + 0x2582c5, + 0x201905, + 0x2cec87, + 0x20afc9, + 0x26e684, + 0x258e45, + 0x2fcfc5, + 0x2e9d08, + 0x28b9c5, + 0x2bd109, + 0x2b3947, + 0x2b394b, + 0x2f24c6, + 0x244949, + 0x3556c8, + 0x291005, + 0x20f3c8, + 0x2e6848, + 0x261fc7, + 0x331bc7, + 0x389149, + 0x26eb07, + 0x29cf89, + 0x2fc3cc, + 0x348188, + 0x2b9649, + 0x2bb207, + 0x283749, + 0x2ff287, + 0x222888, + 0x3b5d45, + 0x3b02c6, + 0x2c44c8, + 0x2d7148, + 0x2caa49, + 0x201947, + 0x272445, + 0x336b09, + 0x2d3206, + 0x293d04, + 0x31bf86, + 0x34f288, + 0x3cbac7, + 0x328548, + 0x219009, + 0x2f8107, + 0x2a1706, + 0x2e6f04, + 0x20cb09, + 0x210f48, + 0x234c47, + 0x36b6c6, + 0x328286, + 0x21cd84, + 0x2f5206, + 0x20f0c3, + 0x32d9c9, + 0x32de06, + 0x2accc5, + 0x2a1e46, + 0x2cf205, + 0x2826c8, + 0x20edc7, + 0x238ec6, + 0x2fee06, + 0x30ffc8, + 0x2a4747, + 0x29ae85, + 0x29ecc8, + 0x3a77c8, + 0x38ee48, + 0x244d85, + 0x3b0346, + 0x308fc9, + 0x2d52c4, + 0x2cf08b, + 0x34f94b, + 0x2e3709, + 0x23ffc3, + 0x256085, + 0x2e48c6, + 0x245b08, + 0x304204, + 0x31f2c6, + 0x205449, + 0x2c2f05, + 0x2cebc6, + 0x331706, + 0x2191c4, + 0x29f34a, + 0x2acc08, + 0x2d7146, + 0x3c2785, + 0x344bc7, + 0x35e587, + 0x21f504, + 0x34fb87, + 0x228f44, + 0x228f46, + 0x20eb03, + 0x267c05, + 0x2b1045, + 0x32f108, + 0x27f0c5, + 0x27ba49, + 0x2a62c7, + 0x34d58b, + 0x2a62cc, + 0x2a68ca, + 0x313987, + 0x20cc43, + 0x3897c8, + 0x244f45, + 0x2f9c85, + 0x359644, + 0x222786, + 0x27c946, + 0x2f5247, + 0x33608b, + 0x219cc4, + 0x3ac004, + 0x2c9a44, + 0x2ce986, + 0x212bc4, + 0x225e48, + 0x359445, + 0x21afc5, + 0x371bc7, + 0x2e6f49, + 0x35e345, + 0x38fe4a, + 0x2a4289, + 0x2ae38a, + 0x3c4f49, + 0x352404, + 0x2c9f05, + 0x2c1348, + 0x2d0ecb, + 0x2d5085, + 0x215246, + 0x209744, + 0x27bf86, + 0x2f7f89, + 0x2e07c7, + 0x275dc8, + 0x2a5846, + 0x27ffc7, + 0x282f08, + 0x3903c6, + 0x3bd204, + 0x380547, + 0x36fe85, + 0x382607, + 0x29a404, + 0x2b9b86, + 0x304f88, + 0x29abc8, + 0x2f1887, + 0x31d6c8, + 0x2984c5, + 0x240004, + 0x381ec8, + 0x295e04, + 0x214e85, + 0x305184, + 0x20e987, + 0x28a207, + 0x283888, + 0x2d2cc6, + 0x27f045, + 0x27b848, + 0x248e88, + 0x2a3b49, + 0x34fc46, + 0x22f2c8, + 0x3bf28a, + 0x3da248, + 0x311985, + 0x223e86, + 0x2a4148, + 0x3bd90a, + 0x20d487, + 0x287fc5, + 0x293f08, + 0x2ab804, + 0x24ff86, + 0x2c5f48, + 0x3c6986, + 0x3c9c88, + 0x254747, + 0x3c16c6, + 0x2baac4, + 0x266847, + 0x2b5684, + 0x2f7f47, + 0x36bacd, + 0x266e85, + 0x2d0a4b, + 0x289e06, + 0x24f808, + 0x23ff84, + 0x3c1f86, + 0x27ee06, + 0x210e07, + 0x29a24d, + 0x2fbf87, + 0x2b6188, + 0x285585, + 0x26e048, + 0x2ca386, + 0x298548, + 0x22e4c6, + 0x2f73c7, + 0x283c09, + 0x35a447, + 0x289108, + 0x273d85, + 0x229e88, + 0x34edc5, + 0x26b045, + 0x34c4c5, + 0x215183, + 0x2846c4, + 0x294105, + 0x243789, + 0x36b5c6, + 0x2e0488, + 0x331985, + 0x2b7f47, + 0x3171ca, + 0x2ceb09, + 0x2c8f0a, + 0x2d68c8, + 0x236fcc, + 0x2843cd, + 0x30ad03, + 0x3c9b88, + 0x3d6fc5, + 0x21f746, + 0x3a47c6, + 0x35c045, + 0x3d01c9, + 0x28e9c5, + 0x27b848, + 0x257506, + 0x360146, + 0x2a5049, + 0x3a6787, + 0x294806, + 0x317148, + 0x3a9f08, + 0x2e4f07, + 0x2c328e, + 0x2ca5c5, + 0x3c0ac5, + 0x3c6888, + 0x31a307, + 0x200e42, + 0x2c36c4, + 0x288bca, + 0x234d08, + 0x2e6986, + 0x29c388, + 0x249886, + 0x36a0c8, + 0x2b0388, + 0x26b004, + 0x2b8705, + 0x602c84, + 0x602c84, + 0x602c84, + 0x204ec3, + 0x328106, + 0x27bd06, + 0x2a0fcc, + 0x202f03, + 0x2cab86, + 0x21a9c4, + 0x359b48, + 0x205285, + 0x288cc6, + 0x2c0c08, + 0x2d8046, + 0x238e46, + 0x212bc8, + 0x2d8d07, + 0x26e8c9, + 0x32044a, + 0x2052c4, + 0x228f85, + 0x2f3205, + 0x3598c6, + 0x231906, + 0x2a1b06, + 0x3cc306, + 0x26ea04, + 0x26ea0b, + 0x228d44, + 0x244ac5, + 0x2af585, + 0x219506, + 0x3c6e08, + 0x284287, + 0x32dd84, + 0x25a2c3, + 0x2ab305, + 0x31be47, + 0x28418b, + 0x32f007, + 0x2c0b08, + 0x2bf447, + 0x269406, + 0x27d308, + 0x292a0b, + 0x2abb46, + 0x213a89, + 0x292b85, + 0x31da43, + 0x2cebc6, + 0x254648, + 0x214303, + 0x27d903, + 0x27b386, + 0x249886, + 0x37880a, + 0x27e885, + 0x27ec4b, + 0x2a1d8b, + 0x244043, + 0x206743, + 0x2b3244, + 0x249647, + 0x2546c4, + 0x219344, + 0x2b4e84, + 0x3da548, + 0x3c26c8, + 0x2089c9, + 0x2d3a08, + 0x34c747, + 0x22f846, + 0x2e00cf, + 0x2ca706, + 0x2d6044, + 0x3c250a, + 0x31bd47, + 0x2b5786, + 0x293d49, + 0x208945, + 0x32f245, + 0x208a86, + 0x229fc3, + 0x2ab849, + 0x222686, + 0x218dc9, + 0x39abc6, + 0x267c05, + 0x28c805, + 0x206643, + 0x249788, + 0x32e4c7, + 0x20ef04, + 0x3599c8, + 0x3173c4, + 0x356506, + 0x31cc06, + 0x23b486, + 0x2d89c9, + 0x2f9c05, + 0x29af46, + 0x247d89, + 0x2c9646, + 0x2e01c6, + 0x39f786, + 0x212185, + 0x305186, + 0x2f73c4, + 0x3b5d45, + 0x2c44c4, + 0x2b6b06, + 0x33fa84, + 0x201a43, + 0x287c45, + 0x233c08, + 0x3d2cc7, + 0x304289, + 0x287ec8, + 0x29b651, + 0x33178a, + 0x2f4587, + 0x254886, + 0x21a9c4, + 0x2c45c8, + 0x2b5c48, + 0x29b80a, + 0x2bcecd, + 0x215146, + 0x212cc6, + 0x266906, + 0x21afc7, + 0x2b6245, + 0x251907, + 0x344ec5, + 0x2b3a84, + 0x206686, + 0x2269c7, + 0x2ab54d, + 0x2a4087, + 0x3774c8, + 0x27bb49, + 0x223d86, + 0x36bd85, + 0x23ae44, + 0x34f386, + 0x21f406, + 0x234e86, + 0x29cc08, + 0x223283, + 0x210e03, + 0x343085, + 0x35c6c6, + 0x2b0345, + 0x2a5a48, + 0x2a07ca, + 0x246b04, + 0x359b48, + 0x296188, + 0x219347, + 0x331a49, + 0x2c0808, + 0x2829c7, + 0x2b5106, + 0x3c698a, + 0x34f408, + 0x307009, + 0x2af148, + 0x227f89, + 0x361107, + 0x303505, + 0x2ffd06, + 0x2b2948, + 0x24f988, + 0x313c48, + 0x31c5c8, + 0x244ac5, + 0x200d04, + 0x232588, + 0x23eb84, 0x3c4d44, - 0x3c7607, - 0x3d030a, - 0x20d144, - 0x20eec6, - 0x212fc9, - 0x212fcf, - 0x213ccd, - 0x214886, - 0x21c790, - 0x21cb86, - 0x21d287, - 0x21e347, - 0x21e34f, - 0x21f549, - 0x223f06, - 0x224f07, - 0x224f08, - 0x226e89, - 0x3b9c48, - 0x3ac987, - 0x217283, - 0x22f5c6, - 0x3c5448, - 0x2b854a, - 0x387589, - 0x211c43, - 0x342486, - 0x263e8a, - 0x2ec387, - 0x301fca, - 0x366ece, - 0x21f686, - 0x30f8c7, - 0x22d3c6, - 0x243e86, - 0x39ad8b, - 0x216c8a, - 0x2c638d, - 0x3c4007, - 0x267788, - 0x267789, - 0x26778f, - 0x2ab00c, - 0x269509, - 0x2d368e, - 0x245e4a, - 0x228e06, - 0x3009c6, - 0x31b40c, - 0x31dc0c, - 0x337a48, - 0x35f9c7, - 0x237085, - 0x22adc4, - 0x32fa8e, - 0x264b44, - 0x320907, - 0x3d5d8a, - 0x22de54, - 0x22f00f, - 0x21e508, - 0x22f488, - 0x36174d, - 0x36174e, - 0x22f909, - 0x2309c8, - 0x2309cf, - 0x234acc, - 0x234acf, - 0x235c87, - 0x237eca, - 0x2467cb, - 0x2392c8, - 0x23b187, - 0x25e68d, - 0x32d486, - 0x3b2986, - 0x23e9c9, - 0x259788, - 0x243c88, - 0x243c8e, - 0x2c5e47, - 0x2f6645, - 0x246a85, - 0x20a544, - 0x206946, - 0x28db48, - 0x332f43, - 0x2f190e, - 0x25ea48, - 0x2a5ccb, - 0x2719c7, - 0x225b05, - 0x2877c6, - 0x2aeb87, - 0x3139c8, - 0x268a49, - 0x3cbc05, - 0x288d08, - 0x217b46, - 0x3a79ca, - 0x32f989, - 0x234e89, - 0x234e8b, - 0x33dd48, - 0x330989, - 0x2ed386, - 0x36baca, - 0x2b4aca, - 0x2380cc, - 0x3448c7, - 0x2c91ca, - 0x34a50b, - 0x34a519, - 0x323088, - 0x22a845, - 0x25e846, - 0x2167c9, - 0x2c98c6, - 0x387cca, - 0x211d06, - 0x2227c4, - 0x2cb1cd, - 0x331247, - 0x2227c9, - 0x249845, - 0x249a88, - 0x24a0c9, - 0x24bd44, - 0x24c9c7, - 0x24c9c8, - 0x24db87, - 0x266708, - 0x2529c7, - 0x338285, - 0x25928c, - 0x259989, - 0x2def0a, - 0x3ab249, - 0x2eb249, - 0x38bf4c, - 0x25c58b, - 0x25dbc8, - 0x25ef88, - 0x262984, - 0x286b48, - 0x287dc9, - 0x2b2447, - 0x213206, - 0x29fd87, - 0x291009, - 0x31f6cb, - 0x326207, - 0x390347, - 0x32bc07, - 0x354584, - 0x354585, - 0x2db805, - 0x3577cb, - 0x3b7bc4, - 0x34fa88, - 0x2f478a, - 0x217c07, - 0x3d7a47, - 0x290852, - 0x292a46, - 0x231b06, - 0x31fe4e, - 0x293286, - 0x2967c8, - 0x296e0f, - 0x3549c8, - 0x39b9c8, - 0x343b4a, - 0x343b51, - 0x2a48ce, - 0x20228a, - 0x20228c, - 0x230bc7, - 0x230bd0, - 0x3c1a48, - 0x2a4ac5, - 0x2af8ca, - 0x3c4d8c, - 0x29944d, - 0x3c1cc6, - 0x3c1cc7, - 0x3c1ccc, - 0x3ced4c, - 0x31540c, - 0x2b028b, - 0x38f704, - 0x22d944, - 0x2b1c49, - 0x341647, - 0x39ff49, - 0x2b4909, - 0x2b2047, - 0x2b2206, - 0x2b2209, - 0x2b2603, - 0x37118a, - 0x316987, - 0x36c5cb, - 0x2c620a, - 0x2dc9c4, - 0x37c586, - 0x283a89, - 0x338544, - 0x2f630a, - 0x242b05, - 0x2bfe85, - 0x2bfe8d, - 0x2c01ce, - 0x2c1945, - 0x33e186, - 0x22a3c7, - 0x25950a, - 0x2dafc6, - 0x2e8fc4, - 0x3b2b47, - 0x2cd98b, - 0x266407, - 0x24e444, - 0x310fc6, - 0x310fcd, - 0x2ddfcc, - 0x20cd46, - 0x2f840a, - 0x26ce06, - 0x230448, - 0x380b87, - 0x2bd50a, - 0x23ef46, - 0x203743, - 0x203746, - 0x3c52c8, - 0x2b1dca, - 0x287107, - 0x287108, - 0x2d3344, - 0x28fe87, - 0x2d6748, - 0x29bcc8, - 0x272788, - 0x2d1cca, - 0x2e1985, - 0x2e1c07, - 0x2566d3, - 0x26b306, - 0x376588, - 0x221349, - 0x2431c8, - 0x23c08b, - 0x39d088, - 0x287cc4, - 0x3d2b06, - 0x319406, - 0x30e6c9, - 0x3d7fc7, - 0x259388, - 0x29be46, - 0x200bc4, - 0x3a5005, - 0x2cef08, - 0x201d8a, - 0x2cae48, - 0x2d0246, - 0x29cc4a, - 0x28e8c8, - 0x2d57c8, - 0x2d6d48, - 0x2d7546, - 0x2da186, - 0x3aad0c, - 0x2da650, - 0x2a2c05, - 0x3547c8, - 0x3b48d0, - 0x3547d0, - 0x3a5dce, - 0x3aa98e, - 0x3aa994, - 0x3b164f, - 0x3b1a06, - 0x202151, - 0x330b93, - 0x331008, - 0x32efc5, - 0x316308, - 0x383a45, - 0x33580c, - 0x295f49, - 0x22ac09, - 0x2fa547, - 0x265e49, - 0x387887, - 0x35cfc6, - 0x284307, - 0x204005, - 0x208d83, - 0x23fc83, - 0x210484, - 0x34290d, - 0x34944f, - 0x200c05, - 0x335706, - 0x212b07, - 0x2170c7, - 0x207b46, - 0x207b4b, - 0x2a5705, - 0x25af46, - 0x300847, - 0x252ec9, - 0x21fcc6, - 0x389205, - 0x36820b, - 0x3a8f06, - 0x207f45, - 0x241d88, - 0x292808, - 0x2a77cc, - 0x2a77d0, - 0x3524c9, - 0x2b2b47, - 0x34e14b, - 0x2e59c6, - 0x3ac84a, - 0x306a4b, - 0x2e85ca, - 0x2e8846, - 0x2e9205, - 0x3249c6, - 0x27c588, - 0x2fa60a, - 0x3613dc, - 0x2f108c, - 0x2f1388, - 0x22a7c5, - 0x38ef47, - 0x2b7ec6, - 0x367985, - 0x216b46, - 0x207d08, - 0x2bedc7, - 0x2b8188, - 0x26b3ca, - 0x212c0c, - 0x3331c9, - 0x2e2347, - 0x227784, - 0x246b46, - 0x39b54a, - 0x2b4a05, - 0x215a4c, - 0x219408, - 0x28eb48, - 0x227d4c, - 0x3d2c4c, - 0x2dc509, - 0x2dc747, - 0x35b44c, - 0x220284, - 0x24baca, - 0x309ccc, - 0x25204b, - 0x25450b, - 0x254c06, - 0x257c47, - 0x230e07, - 0x230e0f, - 0x303551, - 0x2e03d2, - 0x25a48d, - 0x25a48e, - 0x25a7ce, - 0x3b1808, - 0x3b1812, - 0x262b08, - 0x221987, - 0x250bca, - 0x2a86c8, - 0x293245, - 0x2b5e4a, - 0x21cf07, - 0x2e6804, - 0x201783, - 0x237b05, - 0x343dc7, - 0x306547, - 0x29964e, - 0x31e1cd, - 0x3372c9, - 0x248405, - 0x356b83, - 0x34bf46, - 0x25b545, - 0x2a5f08, - 0x321b09, - 0x25e885, - 0x25e88f, - 0x2d9047, - 0x2138c5, - 0x27238a, - 0x3dc646, - 0x2f3f49, - 0x38200c, - 0x3cbd09, - 0x205b86, - 0x2f458c, - 0x33d6c6, - 0x3013c8, - 0x301cc6, - 0x340fc6, - 0x2b6944, - 0x315343, - 0x318f4a, - 0x32bf11, - 0x26c5ca, - 0x25c9c5, - 0x27e1c7, - 0x257107, - 0x2d6844, - 0x2d684b, - 0x209908, - 0x2bdbc6, - 0x233d05, - 0x322804, - 0x234449, - 0x2008c4, - 0x243ac7, - 0x349b05, - 0x349b07, - 0x320085, - 0x2535c3, - 0x221848, - 0x31650a, - 0x2172c3, - 0x2172ca, - 0x279006, - 0x25e60f, - 0x3d16c9, - 0x2f1890, - 0x2f7908, - 0x2d0749, - 0x29a347, - 0x310f4f, - 0x3977c4, - 0x2dbb84, - 0x21ca06, - 0x3ac686, - 0x2e7e8a, - 0x250346, - 0x398f87, - 0x30a7c8, - 0x30a9c7, - 0x30be87, - 0x30d84a, - 0x30c78b, - 0x32b1c5, - 0x2e0008, - 0x21b803, - 0x3c02cc, - 0x361acf, - 0x236e8d, - 0x257707, - 0x337409, - 0x22bac7, - 0x240b48, - 0x22e04c, - 0x287bc8, - 0x23ba88, - 0x328d4e, - 0x347494, - 0x3479a4, - 0x35e78a, - 0x37eacb, - 0x387944, - 0x387949, - 0x2eda08, - 0x247245, - 0x332a4a, - 0x291287, - 0x21f004, - 0x24af03, - 0x22f743, - 0x2375c4, - 0x234e83, - 0x224943, - 0x221b84, - 0x214503, - 0x211d83, - 0x2da646, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x214e03, + 0x267c05, + 0x295647, + 0x2e6d09, + 0x210c07, + 0x21a5c5, + 0x278886, + 0x368186, + 0x213bc4, + 0x2a5386, + 0x27e044, + 0x292686, + 0x2e6ac6, + 0x214146, + 0x3bfc85, + 0x2a5907, + 0x20cc43, + 0x20a909, + 0x30fdc8, + 0x282844, + 0x28284d, + 0x29acc8, + 0x2f0148, + 0x306f86, + 0x283d09, + 0x2ceb09, + 0x2f7c85, + 0x2a08ca, + 0x26da4a, + 0x270c0c, + 0x270d86, + 0x2794c6, + 0x2caf86, + 0x39b709, + 0x21f986, + 0x222906, + 0x28ea86, + 0x34d748, + 0x31d6c6, + 0x2d4b8b, + 0x2957c5, + 0x21afc5, + 0x279cc5, + 0x340c06, + 0x215103, + 0x23b406, + 0x2a4007, + 0x2c4485, + 0x211e45, + 0x3b8505, + 0x33d006, + 0x2f7d44, + 0x334606, + 0x2a9789, + 0x340a8c, + 0x2b37c8, + 0x2a98c4, + 0x304e86, + 0x289f06, + 0x254648, + 0x225fc8, + 0x340989, + 0x344bc7, + 0x236589, + 0x271b06, + 0x2150c4, + 0x205fc4, + 0x281fc4, + 0x282f08, + 0x2e6b4a, + 0x35e2c6, + 0x36b487, + 0x382887, + 0x244a45, + 0x2f31c4, + 0x290c86, + 0x2b6286, + 0x20eec3, + 0x30fc07, + 0x3d35c8, + 0x2f7dca, + 0x345188, + 0x2dd348, + 0x33fac5, + 0x2a1405, + 0x2364c5, + 0x244e06, + 0x35cb06, + 0x2fe385, + 0x32dc09, + 0x2f2fcc, + 0x35b4c7, + 0x29b888, + 0x276705, + 0x602c84, + 0x229cc4, + 0x283484, + 0x218bc6, + 0x2a2d4e, + 0x32f2c7, + 0x21b1c5, + 0x2d524c, + 0x30af87, + 0x226947, + 0x22bb09, + 0x220d49, + 0x287fc5, + 0x30fdc8, + 0x308fc9, + 0x38ed05, + 0x2c43c8, + 0x2b9886, + 0x382146, + 0x23f204, + 0x28fe08, + 0x223f43, + 0x209284, + 0x2ab385, + 0x394e47, + 0x26bcc5, + 0x3bf149, + 0x2a5f8d, + 0x2c6506, + 0x3c37c4, + 0x226b08, + 0x20ae0a, + 0x21bf47, + 0x36ba05, + 0x2092c3, + 0x2a1f4e, + 0x24988c, + 0x305387, + 0x2a2f07, + 0x4230e9c7, + 0x14f0c6, + 0x46e44, + 0x210d83, + 0x21f9c5, + 0x283485, + 0x29c748, + 0x299d49, + 0x3db306, + 0x2546c4, + 0x2f44c6, + 0x266e0b, + 0x2dc50c, + 0x24b8c7, + 0x2d4e45, + 0x3a76c8, + 0x2e4cc5, + 0x3c2507, + 0x3315c7, + 0x22ee45, + 0x215103, + 0x20fd44, + 0x3cb985, + 0x26e585, + 0x26e586, + 0x2a8f48, + 0x2269c7, + 0x3a4ac6, + 0x21cc86, + 0x34c406, + 0x2671c9, + 0x2111c7, + 0x27e146, + 0x2dc686, + 0x277146, + 0x2acb45, + 0x205746, + 0x383a05, + 0x28ba48, + 0x29528b, + 0x2909c6, + 0x3828c4, + 0x2d5b09, + 0x2a62c4, + 0x2b9808, + 0x31c087, + 0x285304, + 0x2bff48, + 0x2c59c4, + 0x2acb84, + 0x398305, + 0x31c4c6, + 0x3da487, + 0x24e4c3, + 0x2a17c5, + 0x2fb684, + 0x3c0b06, + 0x2f7d08, + 0x3db085, + 0x294f49, + 0x313985, + 0x3736c8, + 0x21a887, + 0x32df08, + 0x2bfb87, + 0x2f2909, + 0x282b06, + 0x341c06, + 0x28ea84, + 0x3abf45, + 0x30d1cc, + 0x279cc7, + 0x27a747, + 0x231548, + 0x2c6506, + 0x2a3f44, + 0x34ab44, + 0x388fc9, + 0x2cb086, + 0x26df07, + 0x210a44, + 0x261606, + 0x3a4405, + 0x2d2807, + 0x2d4b06, + 0x2645c9, + 0x2cfa47, + 0x297a87, + 0x2a4ec6, + 0x261545, + 0x281448, + 0x222508, + 0x22fa46, + 0x3db0c5, + 0x2c7406, + 0x2017c3, + 0x29c5c9, + 0x2a188e, + 0x2bf2c8, + 0x3174c8, + 0x22f84b, + 0x295186, + 0x379b04, + 0x238e44, + 0x2a198a, + 0x213147, + 0x27e205, + 0x213a89, + 0x2c31c5, + 0x3c4d87, + 0x230504, + 0x299187, + 0x214fc8, + 0x2cc646, + 0x2b9d09, + 0x2c090a, + 0x2130c6, + 0x29a806, + 0x2af505, + 0x396405, + 0x34bac7, + 0x242788, + 0x3a4348, + 0x26b006, + 0x28c885, + 0x23168e, + 0x226384, + 0x22f9c5, + 0x278209, + 0x2d7688, + 0x28d406, + 0x29e7cc, + 0x2a03d0, + 0x2a298f, + 0x2a44c8, + 0x313987, + 0x3bfc85, + 0x294105, + 0x3da309, + 0x294109, + 0x278f46, + 0x2d5107, + 0x3abe45, + 0x306a89, + 0x353186, + 0x21f7cd, + 0x281e89, + 0x219344, + 0x2bf048, + 0x232649, + 0x35e486, + 0x3899c5, + 0x341c06, + 0x275c89, + 0x27b108, + 0x209a85, + 0x28fe04, + 0x29e98b, + 0x35e345, + 0x245b86, + 0x284706, + 0x252a06, + 0x2a388b, + 0x295049, + 0x21cbc5, + 0x391d47, + 0x331706, + 0x212dc6, + 0x283208, + 0x2b5209, + 0x37728c, + 0x31bc48, + 0x317f06, + 0x338fc3, + 0x22d046, + 0x2a36c5, + 0x27fb48, + 0x28c286, + 0x2d2a48, + 0x242b85, + 0x292745, + 0x21a9c8, + 0x3a9dc7, + 0x3a4707, + 0x2f5247, + 0x319dc8, + 0x313ac8, + 0x2b5b46, + 0x2b6947, + 0x306387, + 0x2a358a, + 0x206383, + 0x340c06, + 0x231605, + 0x288bc4, + 0x27bb49, + 0x2f2884, + 0x202244, + 0x2a0c44, + 0x2a2f0b, + 0x32e407, + 0x2318c5, + 0x2981c8, + 0x278886, + 0x278888, + 0x27e7c6, + 0x28fd45, + 0x290005, + 0x2915c6, + 0x2937c8, + 0x293c88, + 0x27bd06, + 0x29800f, + 0x29c090, + 0x3c12c5, + 0x20cc43, + 0x22aa85, + 0x31ad88, + 0x294009, + 0x38ee48, + 0x2d4f08, + 0x31f888, + 0x32e4c7, + 0x278549, + 0x2d2c48, + 0x285a84, + 0x2a0ac8, + 0x2e9dc9, + 0x2b7607, + 0x2b0104, + 0x210cc8, + 0x2a56ca, + 0x2fb906, + 0x215146, + 0x34fb09, + 0x2a0607, + 0x2d0308, + 0x230588, + 0x21d348, + 0x37f785, + 0x207685, + 0x21afc5, + 0x283445, + 0x2b4a07, + 0x244bc5, + 0x2c4485, + 0x3cfec6, + 0x38ed87, + 0x2d0e07, + 0x2a59c6, + 0x2d6e05, + 0x245b86, + 0x20ee45, + 0x2bca88, + 0x3abdc4, + 0x2c96c6, + 0x324c84, + 0x2bfd48, + 0x2c97ca, + 0x27c48c, + 0x336285, + 0x21b086, + 0x377446, + 0x28e886, + 0x317f84, + 0x3a4d85, + 0x27dd87, + 0x2a0689, + 0x2cef47, + 0x602c84, + 0x602c84, + 0x32e285, + 0x217684, + 0x29dd4a, + 0x278706, + 0x308dc4, + 0x3b06c5, + 0x2b41c5, + 0x2b6184, + 0x284347, + 0x336c87, + 0x2ce988, + 0x2c7508, + 0x209a89, + 0x295e08, + 0x29df0b, + 0x26f484, + 0x2921c5, + 0x389385, + 0x2f51c9, + 0x2b5209, + 0x2d5a08, + 0x228d48, + 0x219504, + 0x289f45, + 0x218e03, + 0x359885, + 0x29afc6, + 0x299b8c, + 0x210946, + 0x3898c6, + 0x28d685, + 0x33d088, + 0x3d83c6, + 0x254a06, + 0x215146, + 0x26368c, + 0x389444, + 0x34c54a, + 0x28d5c8, + 0x2999c7, + 0x2fb586, + 0x3db3c7, + 0x2f40c5, + 0x36b6c6, + 0x366906, + 0x376047, + 0x2c0604, + 0x20ea85, + 0x278204, + 0x2b3b07, + 0x278448, + 0x27934a, + 0x2820c7, + 0x2ac747, + 0x313907, + 0x2e4e09, + 0x299b8a, + 0x229a03, + 0x3d2c85, + 0x214183, + 0x2b4ec9, + 0x361248, + 0x295bc7, + 0x38ef49, + 0x222606, + 0x3b5e08, + 0x393885, + 0x248f8a, + 0x3b0a49, + 0x2492c9, + 0x3d8287, + 0x2b5d49, + 0x214048, + 0x36a2c6, + 0x21b248, + 0x212187, + 0x26eb07, + 0x2a4287, + 0x2d1c48, + 0x3cd4c6, + 0x2a5485, + 0x27dd87, + 0x29a308, + 0x34c384, + 0x2fd284, + 0x294707, + 0x2b0707, + 0x308e4a, + 0x36a246, + 0x32f70a, + 0x2c3607, + 0x226147, + 0x20eb44, + 0x29d044, + 0x2d2706, + 0x36c404, + 0x36c40c, + 0x308d05, + 0x214e09, + 0x2b3644, + 0x2b6245, + 0x20ad88, + 0x293d45, + 0x38fe46, + 0x294244, + 0x2ad88a, + 0x2b3e46, + 0x293a0a, + 0x3c5107, + 0x2d0145, + 0x229fc5, + 0x244a8a, + 0x293945, + 0x2a3c06, + 0x23eb84, + 0x2b33c6, + 0x34bb85, + 0x28c346, + 0x2f188c, + 0x26390a, + 0x26db44, + 0x22f846, + 0x2a0607, + 0x2d4a84, + 0x34d748, + 0x2e7f46, + 0x382709, + 0x2c20c9, + 0x348289, + 0x2cf246, + 0x212286, + 0x21b387, + 0x32db48, + 0x212089, + 0x32e407, + 0x298346, + 0x280047, + 0x2667c5, + 0x226384, + 0x21af47, + 0x306545, + 0x288b05, + 0x200cc7, + 0x22ed08, + 0x3a7646, + 0x29b14d, + 0x29c94f, + 0x2a1d8d, + 0x205484, + 0x233d06, + 0x2d9448, + 0x28ea45, + 0x2a3748, + 0x261e8a, + 0x219344, + 0x2b53c6, + 0x2d60c7, + 0x219cc7, + 0x2d8dc9, + 0x21b205, + 0x2b6184, + 0x2b864a, + 0x2c03c9, + 0x2b5e47, + 0x2f2dc6, + 0x35e486, + 0x289e86, + 0x380606, + 0x2d868f, + 0x2d9309, + 0x31d6c6, + 0x388c06, + 0x32d209, + 0x2b6a47, + 0x214703, + 0x243846, + 0x208a43, + 0x35bf08, + 0x27fe87, + 0x2a46c9, + 0x31ca88, + 0x3a4848, + 0x2ff3c6, + 0x210889, + 0x35b405, + 0x22cf84, + 0x3035c7, + 0x39b785, + 0x205484, + 0x231988, + 0x20f104, + 0x2b6787, + 0x36d146, + 0x269e45, + 0x2af148, + 0x35e34b, + 0x3102c7, + 0x244d06, + 0x2ca784, + 0x379a86, + 0x267c05, + 0x306545, + 0x2811c9, + 0x283f49, + 0x26eb44, + 0x26eb85, + 0x22f885, + 0x248e06, + 0x30fec8, + 0x2c29c6, + 0x3d340b, + 0x384fca, + 0x2bfc85, + 0x290086, + 0x246805, + 0x2db7c5, + 0x296307, + 0x340e88, + 0x236584, + 0x261a86, + 0x293d06, + 0x214207, + 0x31da04, + 0x27ee06, + 0x21f245, + 0x21f249, + 0x212484, + 0x2f3349, + 0x27bd06, + 0x2c51c8, + 0x22f885, + 0x382985, + 0x28c346, + 0x377189, + 0x220d49, + 0x389946, + 0x2d7788, + 0x2a60c8, + 0x2467c4, + 0x2b8ac4, + 0x2b8ac8, + 0x2e6288, + 0x236689, + 0x29af46, + 0x215146, + 0x33860d, + 0x31f2c6, + 0x3215c9, + 0x202945, + 0x208a86, + 0x27b288, + 0x334545, + 0x3063c4, + 0x267c05, + 0x283a88, + 0x29db09, + 0x2782c4, + 0x2b9b86, + 0x3071ca, + 0x305288, + 0x308fc9, + 0x268a0a, + 0x38eec6, + 0x29cb08, + 0x3c22c5, + 0x28d848, + 0x2f4145, + 0x2224c9, + 0x33a989, + 0x20fe02, + 0x292b85, + 0x271786, + 0x27bc47, + 0x322b85, + 0x2fb486, + 0x312c48, + 0x2c6506, + 0x2c1209, + 0x27a846, + 0x283088, + 0x389d05, + 0x3ddc06, + 0x2f74c8, + 0x282f08, + 0x361008, + 0x315708, + 0x205744, + 0x21f543, + 0x2c1444, + 0x2822c6, + 0x266804, + 0x317407, + 0x254909, + 0x2c9a45, + 0x230586, + 0x243846, + 0x2a8d8b, + 0x2b56c6, + 0x33fd06, + 0x2ccd48, + 0x229a46, + 0x2bd1c3, + 0x203e83, + 0x226384, + 0x22f1c5, + 0x2b7ac7, + 0x278448, + 0x27844f, + 0x27dc8b, + 0x30fcc8, + 0x2b9c06, + 0x30ffce, + 0x244ec3, + 0x2b7a44, + 0x2b5645, + 0x2b6006, + 0x290d8b, + 0x295706, + 0x227789, + 0x269e45, + 0x24e408, + 0x204d88, + 0x220c0c, + 0x2a2f46, + 0x3598c6, + 0x2dedc5, + 0x288ec8, + 0x27c485, + 0x351c88, + 0x29eb4a, + 0x2a21c9, + 0x602c84, 0x2000c2, - 0x24af03, - 0x203102, - 0x22f743, - 0x2375c4, - 0x234e83, - 0x224943, - 0x214503, - 0x2da646, - 0x20ce83, - 0x23f7c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x20d343, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x2000c2, - 0x24e8c3, - 0x203102, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x207982, - 0x247502, - 0x203102, - 0x22f743, - 0x2037c2, - 0x2005c2, - 0x221b84, - 0x346484, - 0x2254c2, - 0x209c04, + 0x4820c302, + 0x200382, + 0x224e44, + 0x206a42, + 0x303f84, + 0x205642, + 0xca43, 0x2003c2, - 0x23f7c3, - 0x214e03, - 0x254c06, - 0x210782, - 0x204cc2, - 0x222082, - 0x5421f943, - 0x54602283, - 0x57a46, - 0x57a46, - 0x250784, - 0x204783, - 0x8d9ca, - 0x14b54c, - 0x18840c, - 0xc9e8d, - 0x129245, - 0x8d14c, - 0x26807, - 0xd886, - 0x14a08, - 0x1b587, - 0x20048, - 0x19458a, - 0x109547, - 0x5528d385, - 0xdd949, - 0x3670b, - 0x1872cb, - 0x1c7a88, - 0x11f09, - 0x153a4a, - 0x17f08e, - 0x8f58d, - 0x1442fcb, - 0xdde8a, - 0xc284, - 0x5b086, - 0x12a3c8, - 0x18d648, - 0x39dc7, - 0xac85, - 0x10607, - 0x35309, - 0x1385c7, - 0xfcc8, - 0x229c9, - 0x48cc4, - 0x49945, - 0x16388e, - 0x18d2cd, - 0x11d08, - 0x556a1146, - 0x561669c8, - 0x772c8, - 0x13f6d0, - 0x5504c, - 0x636c7, - 0x64a07, - 0x6a8c7, - 0x73747, - 0x4582, - 0x113707, - 0x16e4c, - 0x17ba45, - 0x126bc7, - 0xa7686, - 0xa8c49, - 0xac2c8, - 0x5dc2, + 0x209482, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x20a803, + 0x216603, + 0x234c83, + 0x242244, + 0x22c0c3, + 0x235604, + 0x232c43, + 0x2db1c4, + 0x228b03, + 0x322e47, + 0x211543, + 0x20ca43, + 0x31b008, + 0x216603, + 0x280acb, + 0x2f55c3, + 0x240986, + 0x219f82, + 0x2eec0b, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x216603, + 0x221003, + 0x204383, + 0x2000c2, + 0x9fe08, + 0x397705, + 0x3065c8, + 0x2e2bc8, + 0x20c302, + 0x329085, + 0x3bfd47, + 0x201bc2, + 0x2401c7, + 0x200382, + 0x254f47, + 0x2bd949, + 0x26c708, + 0x21d1c9, + 0x208582, + 0x3b04c7, + 0x3880c4, + 0x3bfe07, + 0x384ec7, + 0x259902, + 0x211543, + 0x205a02, + 0x205642, + 0x2003c2, + 0x2161c2, + 0x200902, + 0x209482, + 0x2d6405, + 0x21bb85, + 0xc302, + 0x32c43, + 0x22c0c3, + 0x232c43, + 0x210b03, + 0x228b03, + 0x204f43, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x8083, + 0x101, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x214543, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x216e03, + 0x4b50bb86, + 0xe85c3, + 0xca9c5, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x82c2, + 0x9fe08, + 0x12cdc3, + 0xca43, + 0x6d9c3, + 0x42744, + 0x142a744, + 0xe50c5, + 0x2000c2, + 0x392104, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x241f43, + 0x22cd85, + 0x214543, + 0x20e403, + 0x20a803, + 0x24bbc3, + 0x216603, + 0x20c603, + 0x2422c3, + 0x205b03, 0x5c2, - 0x190b46, - 0x1a87cb, - 0x1a8ac6, - 0x175f44, - 0x133a87, + 0x2d7c2, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x2000c2, + 0x248343, + 0x20c302, + 0x232c43, + 0x228b03, + 0x224e44, + 0x20a803, + 0x216603, + 0x209482, + 0x9fe08, + 0x228b03, + 0x6d9c3, + 0x9fe08, + 0x6d9c3, + 0x26fb43, + 0x22c0c3, + 0x22fd84, + 0x232c43, + 0x228b03, + 0x203dc2, + 0x211543, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x203dc2, + 0x238cc3, + 0x20a803, + 0x216603, + 0x2ed343, + 0x20c603, + 0x2000c2, + 0x20c302, + 0x228b03, + 0x20a803, + 0x216603, + 0x240985, + 0x127206, + 0x242244, + 0x219f82, + 0x9fe08, + 0x2000c2, + 0x12eb85, + 0x1c508, + 0x175583, + 0x20c302, + 0x4fd40486, + 0xd944, + 0x10a7cb, + 0x34786, + 0x11647, + 0x1b8dc9, + 0x232c43, + 0x47508, + 0x4750b, + 0x4798b, + 0x480cb, + 0x4840b, + 0x486cb, + 0x48b0b, + 0x7386, + 0x228b03, + 0x20005, + 0x2a44, + 0x20e943, + 0x115547, + 0xded04, + 0x6c144, + 0x20a803, + 0x189a46, + 0x194584, + 0x6d9c3, + 0x216603, + 0x2f61c4, + 0x12ea07, + 0x126e09, + 0x10a588, + 0x52c84, + 0x3e006, + 0x8148, + 0x130245, + 0x3fc9, + 0x2f783, + 0x12eb85, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20ca43, + 0x216603, + 0x2f55c3, + 0x219f82, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x214383, + 0x217b84, + 0x20a803, + 0xca43, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x2db1c4, + 0x228b03, + 0x20a803, + 0x216603, + 0x240986, + 0x232c43, + 0x228b03, + 0x3a183, + 0x6d9c3, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x12eb85, + 0x11647, + 0x7883, + 0x2f783, + 0x9fe08, + 0x228b03, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x5c743, + 0x20a803, + 0x216603, + 0x5322c0c3, + 0x232c43, + 0x20a803, + 0x216603, + 0x9fe08, + 0x2000c2, + 0x20c302, + 0x22c0c3, + 0x228b03, + 0x20a803, + 0x2003c2, + 0x216603, + 0x33aec7, + 0x20f64b, + 0x20c003, + 0x278c08, + 0x32d8c7, + 0x32a1c6, + 0x20d0c5, + 0x3291c9, + 0x2112c8, + 0x37b649, + 0x3a1d90, + 0x37b64b, + 0x2e1b49, + 0x207883, + 0x2ef2c9, + 0x230a06, + 0x230a0c, + 0x3977c8, + 0x3d80c8, + 0x2bde09, + 0x2ba48e, + 0x2bd70b, + 0x2fff4c, + 0x225843, + 0x28768c, + 0x3ce3c9, + 0x308907, + 0x232b8c, + 0x2b18ca, + 0x249f84, + 0x30810d, + 0x287548, + 0x3cee4d, + 0x314846, + 0x24224b, + 0x326f89, + 0x388e87, + 0x369a86, + 0x373a89, + 0x2f790a, + 0x3dcf08, + 0x2f4c84, + 0x38e347, + 0x2417c7, + 0x345784, + 0x217304, + 0x344509, + 0x251789, + 0x28c008, + 0x2eda85, + 0x2084c5, + 0x204c46, + 0x307fc9, + 0x26210d, + 0x215348, + 0x204b47, + 0x20d148, + 0x263e46, + 0x237b84, + 0x285845, + 0x3c4c46, + 0x3c5d44, + 0x3ce2c7, + 0x3d558a, + 0x20ab44, + 0x213006, + 0x213cc9, + 0x213ccf, + 0x214b0d, + 0x215b86, + 0x21c110, + 0x21c506, + 0x21dac7, + 0x220607, + 0x22060f, + 0x221349, + 0x226486, + 0x226fc7, + 0x226fc8, + 0x227ac9, + 0x3a8b48, + 0x311007, + 0x20b383, + 0x22bf46, + 0x298fc8, + 0x2ba74a, + 0x2094c9, + 0x211403, + 0x328f86, + 0x2618ca, + 0x23a747, + 0x30874a, + 0x34018e, + 0x221486, + 0x318947, + 0x34f706, + 0x240d86, + 0x20748b, + 0x39710a, + 0x27630d, + 0x212347, + 0x265388, + 0x265389, + 0x26538f, + 0x30440c, + 0x263289, + 0x3d28ce, + 0x322f4a, + 0x3c2b46, + 0x2fdb86, + 0x31ef4c, + 0x32018c, + 0x322208, + 0x35a347, + 0x37c545, + 0x2297c4, + 0x36a6ce, + 0x262584, + 0x329687, + 0x39d78a, + 0x3d3b54, + 0x3d64cf, + 0x2207c8, + 0x22be08, + 0x3625cd, + 0x3625ce, + 0x22c289, + 0x22d408, + 0x22d40f, + 0x23288c, + 0x23288f, + 0x233a47, + 0x235f0a, + 0x23d64b, + 0x237788, + 0x238b87, + 0x25bfcd, + 0x330106, + 0x3082c6, + 0x23b289, + 0x3dbb88, + 0x240b88, + 0x240b8e, + 0x20f747, + 0x2fbb45, + 0x242505, + 0x207a84, + 0x32a486, + 0x28bf08, + 0x374883, + 0x2e16ce, + 0x25c388, + 0x2a704b, + 0x26fd07, + 0x26ae45, + 0x287806, + 0x2aeb47, + 0x31b4c8, + 0x34b8c9, + 0x3cc205, + 0x287c88, + 0x222e86, + 0x3a35ca, + 0x36a5c9, + 0x232c49, + 0x232c4b, + 0x339748, + 0x345649, + 0x2edb46, + 0x28f0ca, + 0x36764a, + 0x23610c, + 0x368807, + 0x26c50a, + 0x2e5b0b, + 0x2e5b19, + 0x324888, + 0x240a05, + 0x25c186, + 0x217d09, + 0x26cc06, + 0x236d8a, + 0x2114c6, + 0x20d644, + 0x2cbb0d, + 0x344147, + 0x20d649, + 0x244745, + 0x245148, + 0x2472c9, + 0x249204, + 0x249e87, + 0x249e88, + 0x24a2c7, + 0x264208, + 0x24ef47, + 0x36bfc5, + 0x256e4c, + 0x257309, + 0x2d908a, + 0x3a6609, + 0x2ef3c9, + 0x3889cc, + 0x25a18b, + 0x25ad08, + 0x25c8c8, + 0x260284, + 0x284fc8, + 0x286209, + 0x2b1987, + 0x213f06, + 0x2a0e07, + 0x29bc49, + 0x20624b, + 0x35cd07, + 0x216687, + 0x3c5247, + 0x3cedc4, + 0x3cedc5, + 0x2daec5, + 0x358e8b, + 0x3b40c4, + 0x326948, + 0x2f9a0a, + 0x222f47, + 0x3c8e87, + 0x290552, + 0x292586, + 0x22f446, + 0x28e40e, + 0x296b86, + 0x296008, + 0x29664f, + 0x3cf208, + 0x3b1148, + 0x34200a, + 0x342011, + 0x2a5c4e, + 0x2536ca, + 0x2536cc, + 0x22d607, + 0x22d610, + 0x3bee08, + 0x2a5e45, + 0x2aee4a, + 0x3c5d8c, + 0x29868d, + 0x3b0c46, + 0x3b0c47, + 0x3b0c4c, + 0x3bd2cc, + 0x36f70c, + 0x2c4c4b, + 0x38c184, + 0x2e5bc4, + 0x2b1189, + 0x34abc7, + 0x37d789, + 0x367489, + 0x2b1587, + 0x2b1746, + 0x2b1749, + 0x2b1b43, + 0x2c660a, + 0x373cc7, + 0x3c05cb, + 0x27618a, + 0x388144, + 0x32fd06, + 0x282349, + 0x36c284, + 0x2f480a, + 0x245005, + 0x2c16c5, + 0x2c16cd, + 0x2c1a0e, + 0x2c1585, + 0x339b86, + 0x240587, + 0x3db90a, + 0x2569c6, + 0x37c044, + 0x30ed87, + 0x2ee38b, + 0x263f07, + 0x24aac4, + 0x27a306, + 0x27a30d, + 0x2dd88c, + 0x20a6c6, + 0x21554a, + 0x229886, + 0x2147c8, + 0x35b747, + 0x2c93ca, + 0x23b006, + 0x212243, + 0x220286, + 0x298e48, + 0x22fb0a, + 0x2d2dc7, + 0x2d2dc8, + 0x25af84, + 0x290ac7, + 0x2d3288, + 0x292788, + 0x2f1b08, + 0x2b808a, + 0x2e2a45, + 0x2db407, + 0x253513, + 0x268f86, + 0x3dabc8, + 0x224609, + 0x240088, + 0x2ff44b, + 0x3a4bc8, + 0x2b92c4, + 0x21aac6, + 0x320906, + 0x31c309, + 0x2c9207, + 0x256f48, + 0x2a1c06, + 0x200bc4, + 0x3a0e45, + 0x2cf888, + 0x201d8a, + 0x2cb788, + 0x2d0846, + 0x29cd0a, + 0x26e708, + 0x2d4888, + 0x2d6288, + 0x2d6ac6, + 0x2d9646, + 0x3a60cc, + 0x2d9bd0, + 0x2ade05, + 0x3b6408, + 0x3b6410, + 0x3cf010, + 0x3a1c0e, + 0x3a5d4e, + 0x3a5d54, + 0x3ad7cf, + 0x3adb86, + 0x345851, + 0x343a93, + 0x343f08, + 0x369c05, + 0x27aa88, + 0x2097c5, + 0x329d8c, + 0x229189, + 0x229609, + 0x3dd547, + 0x340649, + 0x236947, + 0x35e746, + 0x285647, + 0x203885, + 0x2080c3, + 0x23a183, + 0x20fc44, + 0x30128d, + 0x34bc8f, + 0x200c05, + 0x333a46, + 0x212887, + 0x397547, + 0x204206, + 0x20420b, + 0x2a6a85, + 0x258b46, + 0x305f87, + 0x24f449, + 0x2211c6, + 0x3855c5, + 0x3ba74b, + 0x3b0946, + 0x2137c5, + 0x23f088, + 0x291988, + 0x29f94c, + 0x29f950, + 0x2a2749, + 0x2b71c7, + 0x2b22cb, + 0x2c1f86, + 0x310eca, + 0x3da94b, + 0x30cc4a, + 0x2eca86, + 0x2ed205, + 0x32d7c6, + 0x286bc8, + 0x3dd60a, + 0x36225c, + 0x2f568c, + 0x2f5988, + 0x240985, + 0x38b9c7, + 0x2ba0c6, + 0x3b9505, + 0x218086, + 0x2043c8, + 0x2c0647, + 0x2ba388, + 0x26904a, + 0x3a978c, + 0x374b09, + 0x3a9a07, + 0x286744, + 0x2425c6, + 0x300b4a, + 0x367585, + 0x216f8c, + 0x21a0c8, + 0x2e4ac8, + 0x34ea0c, + 0x35a64c, + 0x387c89, + 0x387ec7, + 0x370b8c, + 0x222104, + 0x24a04a, + 0x30f80c, + 0x25038b, + 0x250a0b, + 0x253c46, + 0x256b07, + 0x22d847, + 0x22d84f, + 0x309cd1, + 0x2dfa92, + 0x257bcd, + 0x257bce, + 0x257f0e, + 0x3ad988, + 0x3ad992, + 0x260408, + 0x224c47, + 0x24d44a, + 0x2a95c8, + 0x296b45, + 0x2b484a, + 0x21c887, + 0x2e99c4, + 0x201783, + 0x235b45, + 0x342287, + 0x355047, + 0x29888e, + 0x3355cd, + 0x33c809, + 0x319c85, + 0x358243, + 0x34a646, + 0x259145, + 0x2a7288, + 0x21e489, + 0x25c1c5, + 0x25c1cf, + 0x2d2547, + 0x20cf45, + 0x2706ca, + 0x3c1586, + 0x245e09, + 0x37818c, + 0x3addc9, + 0x3d7046, + 0x2f980c, + 0x3390c6, + 0x307648, + 0x2e5a06, + 0x3645c6, + 0x2b5844, + 0x31c283, + 0x221a4a, + 0x3037d1, + 0x26344a, + 0x246685, + 0x25a5c7, + 0x254047, + 0x2d3384, + 0x2d338b, + 0x21d048, + 0x2bf146, + 0x2315c5, + 0x32b944, + 0x2410c9, + 0x2008c4, + 0x20c3c7, + 0x34cf05, + 0x34cf07, + 0x28e645, + 0x247d03, + 0x224b08, + 0x27ac8a, + 0x24e4c3, + 0x39774a, + 0x36c746, + 0x25bf4f, + 0x3d2009, + 0x2e1650, + 0x2fcbc8, + 0x2d1689, + 0x29a087, + 0x27a28f, + 0x38f304, + 0x2db244, + 0x21c386, + 0x243206, + 0x2ed5ca, + 0x252f86, + 0x395207, + 0x311348, + 0x311547, + 0x312a07, + 0x314aca, + 0x31330b, + 0x251a45, + 0x2df6c8, + 0x20ae83, + 0x3bcd4c, + 0x37c2cf, + 0x3c0d8d, + 0x257747, + 0x33c949, + 0x2312c7, + 0x267e48, + 0x3d3d4c, + 0x2b91c8, + 0x246388, + 0x33104e, + 0x348c94, + 0x3491a4, + 0x35ff0a, + 0x37bccb, + 0x236a04, + 0x236a09, + 0x2b5448, + 0x242d05, + 0x37438a, + 0x285b07, + 0x322c44, + 0x248343, + 0x22c0c3, + 0x235604, + 0x232c43, + 0x228b03, + 0x224e44, + 0x214543, + 0x211543, + 0x2d9bc6, + 0x217b84, + 0x20a803, + 0x216603, + 0x216103, + 0x2000c2, + 0x248343, + 0x20c302, + 0x22c0c3, + 0x235604, + 0x232c43, + 0x228b03, + 0x214543, + 0x2d9bc6, + 0x20a803, + 0x216603, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x212483, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x217b84, + 0x20a803, + 0x216603, + 0x2000c2, + 0x258783, + 0x20c302, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x204042, + 0x209382, + 0x20c302, + 0x22c0c3, + 0x207902, + 0x2005c2, + 0x224e44, + 0x303f84, + 0x22b302, + 0x217b84, + 0x2003c2, + 0x216603, + 0x216103, + 0x253c46, + 0x20ff42, + 0x205402, + 0x225242, + 0x55a08683, + 0x55e2d603, + 0x54b86, + 0x54b86, + 0x242244, + 0x20ca43, + 0x14114d, + 0x8bd8a, + 0x1bc1cc, + 0x1b338c, + 0xca7cd, + 0x12eb85, + 0x8b50c, + 0x6bb47, + 0xbec6, + 0x15d08, + 0x1ae07, + 0x21ec8, + 0x19974a, + 0x10f087, + 0x56a8b745, + 0xdc1c9, + 0x56c344cb, + 0x920b, + 0x1846c8, + 0x14dd89, + 0x6828a, + 0xe56ce, + 0x7d4d, + 0x2c28d, + 0x143fe8b, + 0xdd74a, + 0xd944, + 0x58c86, + 0x1bd708, + 0x18a0c8, + 0x67607, + 0xa0c5, + 0xfdc7, + 0x330c9, + 0x16c307, + 0xe688, + 0x2b5c9, + 0x4a444, + 0x4d185, + 0x13fe4e, + 0x189d4d, + 0x114c8, + 0x57293106, + 0x57d72f88, + 0x74388, + 0x13b0d0, + 0x510cc, + 0x60fc7, + 0x62447, + 0x68607, + 0x71487, + 0x4c02, + 0x122587, + 0x1972cc, + 0xfe445, + 0x35147, + 0xa8946, + 0xaa289, + 0xac0c8, + 0x4742, + 0x5c2, + 0x18ce06, + 0x1ba00b, + 0x1ba306, + 0xbe384, + 0x1753c7, + 0xe3389, + 0x6dc89, + 0x1432c8, + 0x48902, + 0x191f89, + 0xbac8, + 0xe9a8a, + 0x38909, + 0x52946, + 0xce3c9, + 0xdd6c7, + 0xdde09, + 0xdef08, + 0xe0f87, + 0xe29c9, + 0xe8ec5, + 0xe9250, + 0x155886, + 0x175305, + 0x114587, + 0x3950d, + 0x3ef85, + 0xef1c6, + 0xef9c7, + 0xf61d8, + 0x11848, + 0xbe7ca, + 0xa982, + 0x507ca, + 0x6284d, + 0x2e42, + 0x13686, + 0x9d488, + 0xabe4a, + 0x45948, 0x6d309, - 0x53889, - 0x120648, - 0x4b4c2, - 0x195789, - 0xd488, - 0xe7cca, - 0x12eac6, - 0xcd7c9, - 0xdde07, - 0xde549, - 0xdf8c8, - 0xe0887, - 0xe1909, - 0xe4485, - 0xe4810, - 0x1bb506, - 0x1339c5, - 0x930c7, - 0x6d84d, - 0x41c85, - 0xeb046, - 0xeb887, - 0xf2498, - 0x138948, - 0x17638a, - 0x129c2, - 0x542ca, - 0x64e0d, - 0x5c82, - 0x1c6b46, - 0x9d3c8, - 0x18fe88, - 0x70949, - 0x10b488, - 0x74e4e, - 0x70b88, - 0x14bf47, - 0x56766904, - 0xecd4d, - 0xfc9c5, - 0x1769c8, - 0x1ac248, - 0x105546, - 0x13602, - 0x72d04, - 0x64cc6, - 0xfd986, - 0x56934b0b, - 0x8c42, + 0x111788, + 0x7768e, + 0x6d548, + 0x14a647, + 0x58293044, + 0x14e70d, + 0x102f05, + 0x3148, + 0x42dc8, + 0x10c246, + 0x14302, + 0x92c04, + 0x62706, + 0x3e006, + 0x58532e4b, + 0x57c2, 0x401, - 0x5d807, - 0x10d203, - 0x55af2d04, - 0x55e98183, + 0x81, + 0x5a947, + 0x8d9c3, + 0x576f67c4, + 0x57a973c3, 0xc1, - 0x1cf0c6, + 0x1a286, 0xc1, 0x201, - 0x1cf0c6, - 0x10d203, - 0x50803, - 0x81b84, - 0x305c7, - 0x50c7, - 0x145d285, - 0x4ff04, - 0x63807, - 0x3102, - 0x23d544, - 0x22f743, - 0x24f044, - 0x221b84, - 0x20ce83, - 0x221205, - 0x2158c3, - 0x259703, - 0x207ac5, - 0x2050c3, - 0x6e83, - 0x57a2f743, - 0x234e83, - 0x4f044, + 0x1a286, + 0x8d9c3, + 0x422c3, + 0x46e44, + 0x14947, + 0x5b07, + 0x153e145, + 0x4cec4, + 0x61107, + 0xc302, + 0x249f84, + 0x22c0c3, + 0x24b304, + 0x224e44, + 0x20a803, + 0x2244c5, + 0x216e03, + 0x236bc3, + 0x204185, + 0x205b03, + 0xdd43, + 0x5962c0c3, + 0x232c43, + 0x4b304, 0x20c3, - 0x224943, + 0x228b03, 0x200181, - 0xe943, - 0x211d83, - 0x346484, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x202443, - 0xa7c88, + 0xe403, + 0x211543, + 0x303f84, + 0x217b84, + 0x20a803, + 0x4bbc3, + 0x216603, + 0x20c603, + 0x9fe08, 0x2000c2, - 0x24af03, - 0x203102, - 0x22f743, - 0x234e83, - 0x20d343, + 0x248343, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x212483, 0x2005c2, - 0x221b84, - 0x214503, - 0x211d83, - 0x20ce83, - 0x204783, - 0x23f7c3, - 0x2050c3, - 0xa7c88, - 0x1daa47, - 0x3102, - 0x1a4f85, - 0x5518f, - 0xd8ec6, - 0x14441c8, - 0x10b88e, - 0x58a06dc2, - 0x324048, - 0x28e106, - 0x250086, - 0x305fc7, - 0x58e00c82, - 0x593d1548, - 0x210eca, - 0x263148, + 0x224e44, + 0x214543, + 0x211543, + 0x20a803, + 0x20ca43, + 0x216603, + 0x205b03, + 0x17c204, + 0x9fe08, + 0x103c87, + 0xc302, + 0x1a0dc5, + 0x5120f, + 0xd23c6, + 0x14470c8, + 0x11240e, + 0x5a631802, + 0x32ce48, + 0x28c4c6, + 0x24d046, + 0x30e787, + 0x5aa00c82, + 0x5afd1e88, + 0x21068a, + 0x260a48, 0x200ac2, - 0x3167c9, - 0x32b207, - 0x213186, - 0x221589, - 0x2e1d44, - 0x206586, - 0x2c5144, - 0x295a04, - 0x258c89, - 0x309a06, - 0x2270c5, - 0x267345, - 0x22fe87, - 0x2c2207, - 0x292c84, - 0x35aa06, - 0x2f3705, - 0x20fe45, - 0x25ca85, - 0x2b0587, - 0x271805, - 0x24a549, - 0x321605, - 0x313b04, - 0x2daf07, - 0x32e50e, - 0x36afc9, - 0x31fd09, - 0x344706, - 0x240288, - 0x244b0b, - 0x36950c, - 0x34d146, - 0x32cd87, - 0x2b3f45, - 0x215dca, - 0x28dd49, + 0x3c0409, + 0x251a87, + 0x213e86, + 0x224849, + 0x2db544, + 0x3c0306, + 0x2c7144, + 0x203584, + 0x2560c9, + 0x30f546, + 0x229cc5, + 0x264f45, + 0x22cac7, + 0x2c3887, + 0x2f53c4, + 0x35c0c6, + 0x2f9145, + 0x20e805, + 0x246745, + 0x2c4f47, + 0x26fb45, + 0x247749, + 0x329945, + 0x31b604, + 0x256907, + 0x33ed4e, + 0x343689, + 0x28e2c9, + 0x33de06, + 0x23ce88, + 0x3c208b, + 0x36828c, + 0x324046, + 0x2ffe07, + 0x2b34c5, + 0x21730a, + 0x28c109, 0x2013c9, - 0x3d6746, - 0x300605, - 0x246e05, - 0x354009, - 0x25cc0b, - 0x3a9786, - 0x352886, - 0x2044c4, - 0x26de46, - 0x2f66c8, - 0x3ba3c6, - 0x2aa506, - 0x3ce108, - 0x3d4887, - 0x3d6509, - 0x3d9a85, - 0xa7c88, - 0x3cbb84, - 0x30c404, - 0x20b805, - 0x345409, - 0x220d87, - 0x220d8b, - 0x223b8a, - 0x2293c5, - 0x5960f7c2, - 0x2c60c7, - 0x59a2a9c8, - 0x3d6987, - 0x2c8445, - 0x34678a, - 0x3102, - 0x27b24b, - 0x27f0ca, - 0x24a9c6, - 0x207683, - 0x2aee0d, - 0x3ade4c, - 0x3dcb8d, - 0x232985, - 0x27bf05, - 0x332f87, - 0x20a3c9, - 0x210dc6, - 0x2501c5, - 0x2ea2c8, - 0x26dd43, - 0x2f9748, - 0x26dd48, - 0x2c71c7, - 0x36aac8, - 0x3adc49, - 0x2d0f07, - 0x2c58c7, - 0x25d108, - 0x29b744, - 0x29b747, - 0x30d4c8, - 0x35ee06, - 0x37c6cf, - 0x26d507, - 0x35a506, - 0x2dc885, - 0x222203, - 0x248b47, - 0x38b103, - 0x24e006, - 0x24fe06, - 0x251286, - 0x295605, - 0x266703, - 0x395408, - 0x38de49, - 0x39da0b, - 0x251408, - 0x252685, - 0x253d85, - 0x59eb1d82, - 0x2843c9, - 0x221c07, - 0x25afc5, - 0x258b87, - 0x25a346, - 0x383485, - 0x25b38b, - 0x25dbc4, - 0x262d05, - 0x262e47, - 0x279746, - 0x279b85, - 0x286d47, - 0x287947, - 0x2d7f44, - 0x28cf4a, - 0x28ed48, - 0x244dc9, - 0x36a0c5, - 0x2b42c6, - 0x2f688a, - 0x267246, - 0x22e407, - 0x2c954d, - 0x2a5249, - 0x36b2c5, - 0x321d07, - 0x32e908, - 0x330008, - 0x3491c7, - 0x367306, - 0x2195c7, - 0x24f243, - 0x309984, - 0x380f85, - 0x3aa087, - 0x3afcc9, - 0x226488, - 0x22e305, - 0x249204, - 0x25dd85, - 0x266acd, + 0x3d9f06, + 0x305d45, + 0x242885, + 0x355449, + 0x2468cb, + 0x2772c6, + 0x354546, + 0x204b44, + 0x239b06, + 0x2fbbc8, + 0x3bc0c6, + 0x2eb006, + 0x3cf9c8, + 0x3d6e47, + 0x3d9cc9, + 0x3de005, + 0x9fe08, + 0x3cc184, + 0x312f84, + 0x208345, + 0x346b09, + 0x222c07, + 0x222c0b, + 0x225a4a, + 0x2290c5, + 0x5b20b382, + 0x20ebc7, + 0x5b6293c8, + 0x328887, + 0x2dd185, + 0x347e8a, + 0xc302, + 0x2795cb, + 0x27deca, + 0x247bc6, + 0x2081c3, + 0x291f0d, + 0x3c084c, + 0x3c1acd, + 0x2304c5, + 0x27cb85, + 0x3748c7, + 0x207909, + 0x210586, + 0x252e05, + 0x2ec888, + 0x239a03, + 0x2e2ec8, + 0x239a08, + 0x2c8907, + 0x369588, + 0x3afa49, + 0x2fb787, + 0x20f1c7, + 0x33dfc8, + 0x29b484, + 0x29b487, + 0x314748, + 0x360586, + 0x3c3e4f, + 0x22a5c7, + 0x35bbc6, + 0x388005, + 0x2253c3, + 0x243c47, + 0x3874c3, + 0x24a686, + 0x24cdc6, + 0x24db06, + 0x294d45, + 0x264203, + 0x391c08, + 0x38a8c9, + 0x39a24b, + 0x24dc88, + 0x24ec05, + 0x24fe45, + 0x5bab12c2, + 0x285709, + 0x224ec7, + 0x258bc5, + 0x255fc7, + 0x257a86, + 0x3804c5, + 0x258f8b, + 0x25ad04, + 0x260605, + 0x260747, + 0x276c46, + 0x277085, + 0x2851c7, + 0x285cc7, + 0x2d0d84, + 0x28b30a, + 0x28cd48, + 0x3c2349, + 0x394785, + 0x32f406, + 0x2fbd8a, + 0x264e46, + 0x231047, + 0x26c88d, + 0x2a65c9, + 0x390885, + 0x36ac47, + 0x252788, + 0x2f7288, + 0x3a8d07, + 0x3affc6, + 0x2230c7, + 0x24b503, + 0x30f4c4, + 0x37dc05, + 0x3a5447, + 0x3ab0c9, + 0x26b7c8, + 0x230f45, + 0x2530c4, + 0x24de45, + 0x25ca8d, 0x200cc2, - 0x2b71c6, - 0x259b86, - 0x2f8d0a, - 0x394dc6, - 0x39b485, - 0x2c5605, - 0x2c5607, - 0x3a780c, - 0x27714a, - 0x290506, - 0x202fc5, - 0x26dc86, - 0x290687, - 0x292e86, - 0x29550c, - 0x2216c9, - 0x5a3cf547, - 0x2971c5, - 0x2971c6, - 0x297c08, - 0x2bcd85, - 0x2a5985, - 0x2a6c88, - 0x2a6e8a, - 0x5a67bc02, - 0x5aa05942, - 0x2fd185, - 0x26c143, - 0x22bf08, - 0x20bbc3, - 0x2a7104, - 0x2f408b, - 0x3ce688, - 0x31be08, - 0x5af26609, - 0x2ad349, + 0x2b6606, + 0x2d7a86, + 0x301bca, + 0x3915c6, + 0x398245, + 0x2c7605, + 0x2c7607, + 0x3a340c, + 0x27420a, + 0x290206, + 0x21fb05, + 0x239946, + 0x290387, + 0x292d06, + 0x294c4c, + 0x224989, + 0x5be1a707, + 0x296a05, + 0x296a06, + 0x296e48, + 0x245785, + 0x2a6d05, + 0x2a7f48, + 0x2a814a, + 0x5c21efc2, + 0x5c606a02, + 0x3ac085, + 0x266803, + 0x23dc88, + 0x245f43, + 0x2a83c4, + 0x245f4b, + 0x368688, + 0x2b2788, + 0x5cae7489, + 0x2ad149, 0x2adb06, - 0x2ae808, - 0x2aea09, - 0x2afd86, - 0x2aff05, - 0x249346, - 0x2b0749, - 0x2ba387, - 0x2fdf06, - 0x21d0c7, - 0x36c987, - 0x21fac4, - 0x5b338c49, - 0x367bc8, - 0x3d1448, - 0x239f07, - 0x2ca906, - 0x204a09, - 0x250047, - 0x36744a, - 0x37b0c8, - 0x20cf87, - 0x20ed46, - 0x28c74a, - 0x2ff188, - 0x2e68c5, - 0x227385, - 0x355107, - 0x311c49, - 0x3179cb, - 0x36dec8, - 0x321689, - 0x251b47, - 0x2bc08c, - 0x2bc94c, - 0x2bcc4a, - 0x2bcecc, - 0x2c4cc8, - 0x2c4ec8, - 0x2c50c4, - 0x2c6949, - 0x2c6b89, - 0x2c6dca, - 0x2c7049, - 0x2c7387, - 0x3d6d4c, - 0x20d986, - 0x2c8f08, - 0x267306, - 0x3a35c6, - 0x36b1c7, - 0x36bd08, - 0x206b4b, - 0x3d6847, - 0x258949, - 0x27e309, - 0x2845c7, - 0x2c5384, - 0x200fc7, - 0x2fe8c6, - 0x20e3c6, - 0x2f85c5, - 0x3d8108, - 0x295e44, - 0x295e46, - 0x27700b, - 0x371489, - 0x23aec6, - 0x2aa709, - 0x20b8c6, - 0x201f88, - 0x20fb43, - 0x300785, - 0x21bdc9, - 0x221d85, - 0x353404, - 0x2785c6, - 0x237485, - 0x2553c6, - 0x310047, - 0x34a406, - 0x22c7cb, - 0x36b9c7, - 0x247c46, - 0x2dcd86, - 0x22ff46, - 0x292c49, - 0x2f200a, - 0x2bdec5, - 0x3a900d, - 0x2a6f86, - 0x2f4986, - 0x2f1786, - 0x2303c5, - 0x2e4b07, - 0x225dc7, - 0x275c0e, - 0x211d83, - 0x2ca8c9, - 0x333449, - 0x22fbc7, - 0x26bc07, - 0x2a0b85, - 0x22e8c5, - 0x5b73174f, - 0x2d0987, - 0x2d0b48, - 0x2d2284, - 0x2d2586, - 0x5ba46b02, - 0x2d77c6, - 0x2da646, - 0x33360e, - 0x2f958a, - 0x3d1cc6, - 0x21ae4a, - 0x3dc989, - 0x23e105, - 0x3b21c8, - 0x31e486, - 0x29e0c8, - 0x2fbb08, - 0x27ad0b, - 0x3060c5, - 0x271888, - 0x3ce24c, - 0x2c8307, - 0x250b06, - 0x2fa0c8, - 0x206808, - 0x5be4dc02, - 0x20634b, - 0x3d9c89, - 0x28d809, - 0x21bc47, - 0x3a8d48, - 0x5c39b048, - 0x20c8cb, - 0x3204c9, - 0x25c24d, - 0x320ec8, - 0x28c948, - 0x5c601e02, - 0x31fc44, - 0x5ca28002, - 0x3b1fc6, - 0x5ce06ec2, - 0x2ee48a, - 0x2a5806, - 0x26c9c8, - 0x3c11c8, - 0x255d06, - 0x32d286, - 0x2f7686, - 0x2a5e85, - 0x23a104, - 0x5d229a44, - 0x357fc6, - 0x298687, - 0x5d60d5c7, - 0x38d08b, - 0x3d6b89, - 0x27bf4a, - 0x2039c4, - 0x2c5748, - 0x2fdccd, - 0x2efb09, - 0x2efd48, - 0x2effc9, - 0x2f2484, - 0x246684, - 0x39f245, - 0x36988b, - 0x3ce606, - 0x357e05, - 0x2dc089, - 0x35aac8, - 0x229bc4, - 0x215f49, - 0x3d2685, - 0x2c2248, - 0x2c5f87, - 0x320108, - 0x283c86, - 0x3b9b07, - 0x2df3c9, - 0x368389, - 0x207fc5, - 0x2af585, - 0x5da0a202, - 0x3138c4, - 0x212e85, - 0x305ec6, - 0x312845, - 0x2bac47, - 0x2eedc5, - 0x21dd04, - 0x3447c6, - 0x250247, - 0x2331c6, - 0x318745, - 0x20b208, - 0x28e305, - 0x20e8c7, - 0x21f309, - 0x3715ca, - 0x226987, - 0x22698c, - 0x227086, - 0x23f649, - 0x3826c5, - 0x38fc08, - 0x225003, - 0x2ed345, - 0x2fe585, - 0x27da07, - 0x5de01482, - 0x2ea607, - 0x2e5b06, - 0x37e946, - 0x2e9086, - 0x206746, - 0x2fed88, - 0x316445, - 0x35a5c7, - 0x35a5cd, - 0x201783, - 0x20de45, - 0x272147, - 0x2ea948, - 0x271d05, - 0x215048, - 0x39fe46, - 0x2ddb07, - 0x2c8e45, - 0x306146, - 0x395985, - 0x210b8a, - 0x2f3ac6, - 0x26e607, - 0x2d0505, - 0x383d47, - 0x3b2ac4, - 0x353386, - 0x3b2105, - 0x2177cb, - 0x2fe749, - 0x24e9ca, - 0x208048, - 0x304588, - 0x313f4c, - 0x382c47, - 0x308288, - 0x309f88, - 0x30f645, - 0x354eca, - 0x356b89, - 0x5e202302, - 0x3c2a06, - 0x227bc4, - 0x2bd2c9, - 0x3050c9, - 0x2769c7, - 0x2ef507, - 0x2b4789, - 0x2d1ec8, - 0x2d1ecf, - 0x219fc6, - 0x2dd60b, - 0x25b885, - 0x25b887, - 0x3794c9, - 0x2108c6, - 0x215ec7, - 0x2e0745, - 0x232884, - 0x380a46, - 0x220f44, - 0x2b6587, - 0x2b8e88, - 0x5e700508, - 0x300c85, - 0x300dc7, - 0x323209, - 0x206104, - 0x241848, - 0x5ea696c8, - 0x2d6844, - 0x2e6308, - 0x2fba44, + 0x2ae7c8, + 0x2ae9c9, + 0x2af346, + 0x2af4c5, + 0x244246, + 0x2afa09, + 0x2bf9c7, + 0x3ddac6, + 0x2dd507, + 0x2e77c7, + 0x208804, + 0x5ce11b49, + 0x3b9748, + 0x3d1d88, + 0x267747, + 0x2cb246, + 0x3c6b89, + 0x24d007, + 0x3b8fca, + 0x32f548, + 0x3bd087, + 0x3c1086, + 0x27d88a, + 0x241b88, + 0x2d7505, + 0x228345, + 0x3359c7, + 0x316049, + 0x31828b, + 0x354048, 0x3299c9, - 0x3a9305, - 0x5ee1a602, - 0x21a005, - 0x2e5145, - 0x32fe48, - 0x235ac7, - 0x5f2008c2, - 0x229b85, - 0x2d5646, - 0x233306, - 0x313888, - 0x315108, - 0x312806, - 0x3414c6, - 0x3dab09, - 0x37e886, - 0x21078b, - 0x316705, - 0x2a8606, - 0x3be9c8, - 0x335b86, - 0x339f46, - 0x21550a, - 0x20a80a, - 0x25f505, - 0x391307, - 0x2f5f46, - 0x5f6038c2, - 0x272287, - 0x23be05, - 0x2f6804, - 0x2f6805, - 0x2038c6, - 0x274347, - 0x21ca05, - 0x20a984, - 0x2d29c8, - 0x33a005, - 0x3c8e07, - 0x3d36c5, - 0x210ac5, - 0x26f1c4, - 0x26f1c9, - 0x2f3548, - 0x23a886, - 0x3bad86, - 0x28c486, - 0x5fb056c8, - 0x3058c7, - 0x30670d, - 0x306f4c, - 0x307549, - 0x307789, - 0x5ff77902, - 0x3d1203, + 0x24e087, + 0x2bae4c, + 0x2bb8cc, + 0x2bbbca, + 0x2bbe4c, + 0x2c6cc8, + 0x2c6ec8, + 0x2c70c4, + 0x2c8089, + 0x2c82c9, + 0x2c850a, + 0x2c8789, + 0x2c8ac7, + 0x3b4f4c, + 0x3c62c6, + 0x26c248, + 0x264f06, + 0x38ebc6, + 0x390787, + 0x39f348, + 0x32a68b, + 0x3da007, + 0x255d89, + 0x25a709, + 0x285907, + 0x2c7384, + 0x200fc7, + 0x2cfec6, + 0x20c7c6, + 0x215705, + 0x2ce588, + 0x340544, + 0x340546, + 0x2740cb, + 0x2c6909, + 0x31fc86, + 0x2eb209, + 0x208406, + 0x201f88, + 0x20e503, + 0x305ec5, + 0x21b649, + 0x21bec5, + 0x380d84, + 0x2755c6, + 0x2354c5, + 0x207f06, + 0x316887, + 0x34b4c6, + 0x22ab4b, + 0x28efc7, + 0x243946, + 0x272506, + 0x22cb86, + 0x2f5389, + 0x2b884a, + 0x2f9d45, + 0x22850d, + 0x2a8246, + 0x23aec6, + 0x2e1546, + 0x214745, + 0x2e9547, + 0x26b107, + 0x272c8e, + 0x211543, + 0x2cb209, + 0x374d89, + 0x22c807, + 0x269887, + 0x292905, + 0x36b7c5, + 0x5d34464f, + 0x2d18c7, + 0x2d1a88, + 0x2d1fc4, + 0x2d2286, + 0x5d642582, + 0x2d6d46, + 0x2d9bc6, + 0x374f4e, + 0x2e2d0a, + 0x3d2606, + 0x219b8a, + 0x3c18c9, + 0x23bd85, + 0x307b08, + 0x335886, + 0x2b1388, + 0x3dbd48, + 0x27b58b, + 0x30e885, + 0x26fbc8, + 0x3cfb0c, + 0x2dd047, + 0x24d386, + 0x3dd0c8, + 0x32a348, + 0x5da39242, + 0x208ccb, + 0x3de209, + 0x28bbc9, + 0x21b4c7, + 0x3ba588, + 0x5de07748, + 0x20df8b, + 0x343149, + 0x259e4d, + 0x31d7c8, + 0x27da88, + 0x5e201e02, + 0x3c75c4, + 0x5e62d7c2, + 0x3aba86, + 0x5ea06302, + 0x2f258a, + 0x2a6b86, + 0x26a908, + 0x3be5c8, + 0x3c0206, + 0x300306, + 0x2fc946, + 0x2a7205, + 0x237dc4, + 0x5efd3204, + 0x359686, + 0x2978c7, + 0x5f20bc07, + 0x389b0b, + 0x328a89, + 0x27cbca, + 0x220504, + 0x2c7748, + 0x3dd88d, + 0x2f3a89, + 0x2f3cc8, + 0x2f3f49, + 0x2f61c4, + 0x23d504, + 0x39ba85, + 0x275f4b, + 0x368606, + 0x3594c5, + 0x3cb449, + 0x35c188, + 0x2a4984, + 0x217489, + 0x306845, + 0x2c38c8, + 0x20f887, + 0x28e6c8, + 0x282546, + 0x3a8a07, + 0x2deac9, + 0x3ba8c9, + 0x213845, + 0x322ac5, + 0x5f61df02, + 0x31b3c4, + 0x230705, + 0x30e686, + 0x33cf45, + 0x2b7d87, + 0x2f2ec5, + 0x276c84, + 0x33dec6, + 0x252e87, + 0x251f86, + 0x3ac605, + 0x2098c8, + 0x28c6c5, + 0x20e387, + 0x21d889, + 0x2c6a4a, + 0x227cc7, + 0x227ccc, + 0x229c86, + 0x241d49, + 0x38c685, + 0x2456c8, + 0x202e43, + 0x2edb05, + 0x3a94c5, + 0x27c1c7, + 0x5fa01482, + 0x2ee787, + 0x2e8746, + 0x37fcc6, + 0x2ecbc6, + 0x32a286, + 0x239188, + 0x27abc5, + 0x35bc87, + 0x35bc8d, + 0x201783, + 0x3c6785, + 0x270487, + 0x2eeac8, + 0x270045, + 0x216348, + 0x37d686, + 0x2dc387, + 0x2c9c05, + 0x30e906, + 0x392185, + 0x21034a, + 0x303406, + 0x26ef47, + 0x2c2fc5, + 0x308407, + 0x30ed04, + 0x380d06, + 0x307a45, + 0x397c4b, + 0x2cfd49, + 0x25888a, + 0x2138c8, + 0x38d108, + 0x30bd0c, + 0x30c747, + 0x30fac8, + 0x316608, + 0x3186c5, + 0x3562ca, + 0x358249, + 0x5fe03a42, + 0x206146, + 0x25c1c4, + 0x2f0e09, + 0x25b589, + 0x2712c7, + 0x31d0c7, + 0x367309, + 0x2b8288, + 0x2b828f, + 0x223ac6, + 0x2dbe8b, + 0x259485, + 0x259487, + 0x36c889, + 0x210086, + 0x217407, + 0x2dfe05, + 0x2303c4, + 0x35b606, + 0x222dc4, + 0x2f1347, + 0x321988, + 0x60305c48, + 0x306cc5, + 0x306e07, + 0x324a09, + 0x208a84, + 0x23eb48, + 0x607c2e88, + 0x2d3384, + 0x2ebdc8, + 0x369b44, + 0x34b6c9, + 0x214685, + 0x60a19f82, + 0x223b05, + 0x2e8045, + 0x36aa88, + 0x233887, + 0x60e008c2, + 0x3d3345, + 0x2d4706, + 0x23e306, + 0x31b388, + 0x3192c8, + 0x33cf06, + 0x34aa46, + 0x303d49, + 0x37fc06, + 0x20ff4b, + 0x32a105, + 0x2a9506, + 0x2f8548, + 0x34df46, + 0x313ec6, + 0x216a4a, + 0x2d64ca, + 0x24fb45, + 0x307487, + 0x2fb286, + 0x61217042, + 0x2705c7, + 0x2ff1c5, + 0x2fbd04, + 0x2fbd05, + 0x220406, + 0x272087, + 0x21c385, + 0x25b644, + 0x2e0cc8, + 0x313f85, + 0x3c8647, + 0x3d43c5, + 0x210285, + 0x2c4e84, + 0x2e3cc9, + 0x2f8f88, + 0x238546, + 0x2e9c46, + 0x27d5c6, + 0x6170c3c8, + 0x30c5c7, + 0x30c90d, + 0x30cecc, + 0x30d4c9, + 0x30d709, + 0x61b75ac2, + 0x3d1b43, 0x2010c3, - 0x2fe985, - 0x3aa18a, - 0x341386, - 0x244545, - 0x311304, - 0x31130b, - 0x3344cc, - 0x334dcc, - 0x3350d5, - 0x335fcd, - 0x33b44f, - 0x33b812, - 0x33bc8f, - 0x33c052, - 0x33c4d3, - 0x33c98d, - 0x33cf4d, - 0x33d2ce, - 0x33d84e, - 0x33df4c, - 0x33e30c, - 0x33e74b, - 0x33f1ce, - 0x33fad2, - 0x34114c, - 0x341810, - 0x34d412, - 0x34e4cc, - 0x34eb8d, - 0x34eecc, - 0x3514d1, - 0x352a0d, - 0x35584d, - 0x355e4a, - 0x3560cc, - 0x35758c, - 0x357b0c, - 0x3588cc, - 0x35bd53, - 0x35c3d0, - 0x35c7d0, - 0x35d14d, - 0x35d74c, - 0x35e4c9, - 0x360a4d, - 0x360d93, - 0x362211, - 0x362a13, - 0x363ecf, - 0x36428c, - 0x36458f, - 0x36494d, - 0x364f4f, - 0x365310, - 0x365d8e, - 0x36d30e, - 0x36e510, - 0x36efcd, - 0x36f94e, - 0x36fccc, - 0x371853, - 0x3737ce, - 0x373e50, - 0x374251, - 0x37468f, - 0x374a53, - 0x37748d, - 0x3777cf, - 0x377b8e, - 0x378110, - 0x378509, - 0x379710, - 0x379d0f, - 0x37a38f, - 0x37a752, - 0x37ce4e, - 0x37d84d, - 0x37dfcd, - 0x37e30d, - 0x37f7cd, - 0x37fb0d, - 0x37fe50, - 0x38024b, - 0x380d4c, - 0x3810cc, - 0x3816cc, - 0x3819ce, - 0x390510, - 0x392012, - 0x39248b, - 0x3927ce, - 0x392b4e, - 0x3933ce, - 0x39384b, - 0x60393f56, - 0x394acd, - 0x394f54, - 0x395c4d, - 0x397995, - 0x39950d, - 0x399e8f, - 0x39a54f, - 0x39dccf, - 0x39e08e, - 0x39e60d, - 0x3a0491, - 0x3a2d8c, - 0x3a308c, - 0x3a338b, - 0x3a394c, - 0x3a3fcf, - 0x3a4392, - 0x3a4a4d, - 0x3a5b4c, - 0x3a684c, - 0x3a6b4d, - 0x3a6e8f, - 0x3a724e, - 0x3a9e4c, - 0x3aa40d, - 0x3aa74b, - 0x3ab00c, - 0x3ab90d, - 0x3abc4e, - 0x3abfc9, - 0x3ad753, - 0x3aed8d, - 0x3af48d, - 0x3afa8c, - 0x3aff0e, - 0x3b060f, - 0x3b09cc, - 0x3b0ccd, - 0x3b100f, - 0x3b13cc, - 0x3b2d0c, - 0x3b31cc, - 0x3b34cc, - 0x3b3b8d, - 0x3b3ed2, - 0x3b52cc, - 0x3b55cc, - 0x3b58d1, - 0x3b5d0f, - 0x3b60cf, - 0x3b6493, - 0x3b724e, - 0x3b75cf, - 0x3b798c, - 0x607b7cce, - 0x3b804f, - 0x3b8416, - 0x3b9f92, - 0x3bcc0c, - 0x3befcf, - 0x3bf64d, - 0x3c7e8f, - 0x3c824c, - 0x3c854d, - 0x3c888d, - 0x3c9f0e, - 0x3caa4c, - 0x3cd48c, - 0x3cd790, - 0x3d0591, - 0x3d09cb, - 0x3d0e0c, - 0x3d110e, - 0x3d3b11, - 0x3d3f4e, - 0x3d42cd, - 0x3d830b, - 0x3d8c0f, - 0x3d95d4, - 0x203942, - 0x203942, - 0x226a83, - 0x203942, - 0x226a83, - 0x203942, - 0x207a82, - 0x249385, - 0x3d380c, - 0x203942, - 0x203942, - 0x207a82, - 0x203942, - 0x298285, - 0x3715c5, - 0x203942, - 0x203942, - 0x209002, - 0x298285, - 0x336789, - 0x361f0c, - 0x203942, - 0x203942, - 0x203942, - 0x203942, - 0x249385, - 0x203942, - 0x203942, - 0x203942, - 0x203942, - 0x209002, - 0x336789, - 0x203942, - 0x203942, - 0x203942, - 0x3715c5, - 0x203942, - 0x3715c5, - 0x361f0c, - 0x3d380c, - 0x24af03, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x20ce83, - 0x23f7c3, - 0x61309b07, - 0x1c780f, - 0x131308, - 0x74f84, - 0x4783, - 0x1a2108, - 0x5184, + 0x2cff85, + 0x3a554a, + 0x33cdc6, + 0x23fd45, + 0x317944, + 0x31794b, + 0x33280c, + 0x33310c, + 0x333415, + 0x3342cd, + 0x336e4f, + 0x337212, + 0x33768f, + 0x337a52, + 0x337ed3, + 0x33838d, + 0x33894d, + 0x338cce, + 0x33924e, + 0x33994c, + 0x339d0c, + 0x33a14b, + 0x33abce, + 0x33b4d2, + 0x33cb8c, + 0x33d2d0, + 0x34fdd2, + 0x350c0c, + 0x3512cd, + 0x35160c, + 0x3536d1, + 0x3546cd, + 0x356f0d, + 0x35750a, + 0x35778c, + 0x358c4c, + 0x3591cc, + 0x359d4c, + 0x35d4d3, + 0x35db50, + 0x35df50, + 0x35e8cd, + 0x35eecc, + 0x35fc49, + 0x3618cd, + 0x361c13, + 0x363251, + 0x363a53, + 0x36474f, + 0x364b0c, + 0x364e0f, + 0x3651cd, + 0x3657cf, + 0x365b90, + 0x36660e, + 0x36b18e, + 0x36cad0, + 0x36da4d, + 0x36e3ce, + 0x36e74c, + 0x36fa13, + 0x37180e, + 0x371e90, + 0x372291, + 0x3726cf, + 0x372a93, + 0x37564d, + 0x37598f, + 0x375d4e, + 0x3762d0, + 0x3766c9, + 0x378490, + 0x378a8f, + 0x37910f, + 0x3794d2, + 0x379c8e, + 0x37a68d, + 0x37b00d, + 0x37b34d, + 0x37c70d, + 0x37ca4d, + 0x37cd90, + 0x37d18b, + 0x37d9cc, + 0x37dd4c, + 0x37e34c, + 0x37e64e, + 0x38c7d0, + 0x38e512, + 0x38e98b, + 0x38f4ce, + 0x38f84e, + 0x3900ce, + 0x39054b, + 0x61f909d6, + 0x3912cd, + 0x391754, + 0x39244d, + 0x393ad5, + 0x39578d, + 0x39610f, + 0x3968cf, + 0x39a50f, + 0x39a8ce, + 0x39ae4d, + 0x39ca11, + 0x39eb0c, + 0x39ee0c, + 0x39f10b, + 0x39f54c, + 0x39fbcf, + 0x39ff92, + 0x3a088d, + 0x3a198c, + 0x3a244c, + 0x3a274d, + 0x3a2a8f, + 0x3a2e4e, + 0x3a520c, + 0x3a57cd, + 0x3a5b0b, + 0x3a63cc, + 0x3a6ccd, + 0x3a700e, + 0x3a7389, + 0x3a83d3, + 0x3aa18d, + 0x3aa88d, + 0x3aae8c, + 0x3ab30e, + 0x3ac78f, + 0x3acb4c, + 0x3ace4d, + 0x3ad18f, + 0x3ad54c, + 0x3ae3cc, + 0x3ae88c, + 0x3aeb8c, + 0x3af24d, + 0x3af592, + 0x3b164c, + 0x3b194c, + 0x3b1c51, + 0x3b208f, + 0x3b244f, + 0x3b2813, + 0x3b374e, + 0x3b3acf, + 0x3b3e8c, + 0x623b41ce, + 0x3b454f, + 0x3b4916, + 0x3b5452, + 0x3b7d4c, + 0x3bb14f, + 0x3bb7cd, + 0x3c76cf, + 0x3c7a8c, + 0x3c7d8d, + 0x3c80cd, + 0x3c988e, + 0x3ca3cc, + 0x3cd64c, + 0x3cd950, + 0x3d0ed1, + 0x3d130b, + 0x3d174c, + 0x3d1a4e, + 0x3d4811, + 0x3d4c4e, + 0x3d4fcd, + 0x3d854b, + 0x3d8e4f, + 0x3d9814, + 0x220482, + 0x220482, + 0x227dc3, + 0x220482, + 0x227dc3, + 0x220482, + 0x204142, + 0x244285, + 0x3d450c, + 0x220482, + 0x220482, + 0x204142, + 0x220482, + 0x2974c5, + 0x2c6a45, + 0x220482, + 0x220482, + 0x208342, + 0x2974c5, + 0x334a89, + 0x362f4c, + 0x220482, + 0x220482, + 0x220482, + 0x220482, + 0x244285, + 0x220482, + 0x220482, + 0x220482, + 0x220482, + 0x208342, + 0x334a89, + 0x220482, + 0x220482, + 0x220482, + 0x2c6a45, + 0x220482, + 0x2c6a45, + 0x362f4c, + 0x3d450c, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x20a803, + 0x216603, + 0x62f0f647, + 0x1ce4cf, + 0x144208, + 0x6704, + 0xca43, + 0xcd248, + 0x5bc4, 0x2000c2, - 0x61a03102, - 0x241183, - 0x2539c4, + 0x6360c302, + 0x23e483, + 0x259844, 0x2020c3, - 0x2d4704, - 0x231b06, - 0x3c6f83, - 0x3cb504, - 0x25f205, - 0x211d83, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0x21d64a, - 0x254c06, - 0x392ecc, - 0xa7c88, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x225a83, - 0x2da646, - 0x20ce83, - 0x23f7c3, - 0x214e03, - 0x31e43, - 0xa8248, - 0x625bdb45, - 0x49d07, - 0x129245, - 0x187409, - 0xe2c2, - 0x1b448a, - 0x63383985, - 0x129245, - 0x26807, - 0x70a88, - 0x574e, - 0x8a7d2, - 0x116acb, - 0x109646, - 0x6368d385, - 0x63a8d38c, - 0x16b647, - 0xe747, - 0x14f64a, - 0x3dbd0, - 0x16cb05, - 0x10404b, - 0x18d648, - 0x39dc7, - 0x13a0cb, - 0x35309, - 0x49547, - 0x1385c7, - 0x1a99c7, - 0x36906, - 0xfcc8, - 0x64025786, - 0x18fdc7, - 0x146086, - 0x18d2cd, - 0x138e10, - 0x64469102, - 0x11d08, - 0x40cd0, - 0x18468c, - 0x64b8edcd, - 0x5ba48, - 0x5becb, - 0x6b1c7, - 0x17b589, - 0x57b06, - 0x97e08, - 0x5ce02, - 0x8268a, - 0x2c287, - 0x126bc7, - 0xa8c49, - 0xac2c8, - 0x34c5, - 0x190b46, - 0x1a8ac6, - 0xf0c4e, - 0x1b20e, - 0x2d28f, - 0x6d309, - 0x53889, - 0x8220b, - 0x94b4f, - 0xb0b0c, - 0xbb94b, - 0xdee48, - 0x110647, - 0x15ddc8, - 0x191c4b, - 0x198d4c, - 0x19f54c, - 0x1a3ccc, - 0xb138d, - 0x120648, - 0xea082, - 0x195789, - 0xf3dc8, - 0x1961cb, - 0xcab06, - 0xd4f4b, - 0x13f60b, - 0xdfe8a, - 0xe0a45, - 0xe4810, - 0xe5f86, - 0x129e06, - 0x1339c5, - 0x930c7, - 0xf7d08, - 0xeb887, - 0xebb47, - 0x1c7cc7, - 0xbf9c6, - 0x1b1e0a, - 0xa7b0a, - 0x1c6b46, - 0xab84d, - 0x18fe88, - 0x10b488, - 0xd6ec9, - 0xbaf85, - 0x1b030c, - 0xb158b, - 0x190d44, - 0x105309, - 0x105546, - 0x4ab06, - 0x1b9006, - 0x4cc2, - 0xfd986, - 0x1762cb, - 0x111e87, - 0x8c42, - 0xcc705, - 0x22f04, + 0x2d3b84, + 0x22f446, + 0x20b8c3, + 0x30b0c4, + 0x398685, + 0x211543, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x21de8a, + 0x253c46, + 0x38fbcc, + 0x9fe08, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x238cc3, + 0x2d9bc6, + 0x20a803, + 0x216603, + 0x216103, + 0x2f783, + 0xa9148, + 0x641c6005, + 0x453c7, + 0x12eb85, + 0x9349, + 0xc6c2, + 0x1b5fca, + 0x64f9e785, + 0x12eb85, + 0x6bb47, + 0x6d448, + 0x680e, + 0x894d2, + 0x173e0b, + 0x10f186, + 0x6528b745, + 0x6568b74c, + 0x8ec47, + 0x176c87, + 0x12650a, + 0x3a210, + 0xe7945, + 0x10a7cb, + 0x18a0c8, + 0x67607, + 0x11404b, + 0x330c9, + 0x44447, + 0x16c307, + 0x77507, + 0x346c6, + 0xe688, + 0x65c39346, + 0x45887, + 0x147786, + 0x189d4d, + 0xc78d0, + 0x66009802, + 0x114c8, + 0x67fd0, + 0x181a8c, + 0x6678b84d, + 0x59648, + 0x59acb, + 0x68e47, + 0x6e149, + 0x54c46, + 0x97048, + 0x33c2, + 0x198c0a, + 0x1cb807, + 0x35147, + 0xaa289, + 0xac0c8, + 0x20005, + 0x18ce06, + 0x1ba306, + 0x100d4e, + 0x240ce, + 0x14f5cf, + 0xe3389, + 0x6dc89, + 0x19878b, + 0xa318f, + 0x15090c, + 0xc010b, + 0xd8fc8, + 0x116e87, + 0x15f548, + 0x18e14b, + 0x194fcc, + 0x19bd8c, + 0x19f8cc, + 0xb08cd, + 0x1432c8, + 0xf10c2, + 0x191f89, + 0x45c88, + 0x19e10b, + 0xcb446, + 0xd408b, + 0x13b00b, + 0xdf54a, + 0xe1145, + 0xe9250, + 0xeba46, + 0x52286, + 0x175305, + 0x114587, + 0xd6fc8, + 0xef9c7, + 0xefc87, + 0x184907, + 0xc10c6, + 0x1ab8ca, + 0x9fc8a, + 0x13686, + 0xad44d, + 0x45948, + 0x111788, + 0x112009, + 0xb9545, + 0x1a214c, + 0xb0acb, + 0x1cab84, + 0x10c009, + 0x10c246, + 0x4a506, + 0x1bff46, + 0x5402, + 0x3e006, + 0xbe70b, + 0x118547, + 0x57c2, + 0xccc85, + 0x63444, 0x101, - 0x54283, - 0x63e6c306, - 0x98183, + 0x50343, + 0x65a669c6, + 0x973c3, 0x382, - 0x22b04, + 0x2b704, 0xac2, - 0x50784, + 0x42244, 0x882, - 0x4602, + 0x4c82, 0x19c2, - 0x1c342, - 0x7982, - 0x8d382, + 0x27682, + 0x4042, + 0x8b742, 0xd42, - 0x291c2, - 0x38142, - 0x24502, - 0x7682, - 0x50002, - 0x34e83, + 0x8cac2, + 0x36182, + 0x59dc2, + 0x81c2, + 0x4cfc2, + 0x32c43, 0x942, 0x1bc2, - 0x143c2, - 0x8142, + 0xc202, + 0x3d42, 0x642, - 0x33702, - 0x5dc2, + 0x30ac2, + 0x4742, 0x1cc2, 0xf42, 0x5c2, - 0x14503, + 0x14543, 0x1742, - 0x51c2, - 0x4b4c2, - 0x51b42, - 0xe942, - 0x5542, - 0x65c2, - 0x30c2, - 0x5982, - 0x127e02, - 0x6ff42, - 0x3c9c2, - 0xce83, + 0x2cc2, + 0x48902, + 0x4e082, + 0x3102, + 0x5f82, + 0x17002, + 0x1fc02, + 0x6a42, + 0x140d82, + 0x6bf42, + 0x9082, + 0xa803, 0x602, - 0x4dc02, + 0x39242, 0x2f42, - 0xdbc2, - 0x7f45, - 0x7702, - 0x3542, - 0x3e903, + 0x23242, + 0x137c5, + 0x8242, + 0x20082, + 0x3b1c3, 0x682, - 0x129c2, - 0x5c82, + 0xa982, + 0x2e42, 0x1702, 0x1782, 0x8c2, - 0x13602, - 0x4cc2, - 0xe745, - 0x64e07a82, - 0x652cc0c3, - 0x31643, - 0x65607a82, - 0x31643, - 0x83107, - 0x20b7c3, + 0x14302, + 0x5402, + 0x7d45, + 0x66a04142, + 0x66f6d603, + 0x13583, + 0x67204142, + 0x13583, + 0x819c7, + 0x209e83, 0x2000c2, - 0x22f743, - 0x234e83, - 0x20d343, + 0x22c0c3, + 0x232c43, + 0x212483, 0x2005c3, - 0x225a83, - 0x20ce83, - 0x204783, - 0x23f7c3, - 0x2981c3, - 0xf6585, - 0xd103, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x20d343, - 0x211d83, - 0x20ce83, - 0x204783, - 0x71003, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, + 0x238cc3, + 0x20a803, + 0x20ca43, + 0x216603, + 0x297403, + 0xfba85, + 0x8303, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x212483, + 0x211543, + 0x20a803, + 0x20ca43, + 0x6d9c3, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, 0x200181, - 0x211d83, - 0x20ce83, - 0x22c483, - 0x23f7c3, - 0x177384, - 0x24af03, - 0x22f743, - 0x234e83, - 0x211d03, - 0x20d343, - 0x35b003, - 0x207c03, - 0x2a57c3, - 0x228c43, - 0x224943, - 0x221b84, - 0x20ce83, - 0x23f7c3, - 0x2050c3, - 0x330684, - 0x22cf03, - 0x1c183, - 0x3c5243, - 0x3226c8, - 0x28c784, + 0x211543, + 0x20a803, + 0x24bbc3, + 0x216603, + 0xbef44, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x20d083, + 0x212483, + 0x35c6c3, + 0x2042c3, + 0x2a6b43, + 0x20edc3, + 0x228b03, + 0x224e44, + 0x20a803, + 0x216603, + 0x205b03, + 0x345344, + 0x25cc83, + 0x25843, + 0x2287c3, + 0x32b808, + 0x27d8c4, 0x20020a, - 0x23ac46, - 0x123004, - 0x38e307, - 0x21e64a, - 0x219e89, - 0x3b4307, - 0x3b894a, - 0x24af03, - 0x2fd20b, - 0x228b89, - 0x2d6645, - 0x3b3007, - 0x3102, - 0x22f743, - 0x224507, - 0x26e885, - 0x2c5249, - 0x234e83, - 0x375106, - 0x2c4683, - 0xe5b83, - 0x10ed86, - 0x1327c6, - 0x1c6f07, - 0x217d86, - 0x21c385, - 0x3d9b47, - 0x30bcc7, - 0x68224943, - 0x34e707, - 0x3b9103, - 0x20afc5, - 0x221b84, - 0x271508, - 0x37d54c, - 0x2b28c5, - 0x2a53c6, - 0x2243c7, - 0x2e2407, - 0x25f607, - 0x263b48, - 0x30dccf, - 0x21a0c5, - 0x241287, - 0x2085c7, - 0x2a724a, - 0x2ea109, - 0x319ac5, - 0x31b98a, - 0x148f06, - 0xbb447, - 0x2c4705, - 0x3926c4, - 0x255c46, - 0xc8506, - 0x384907, - 0x2e9f87, - 0x36ac88, - 0x218305, - 0x26e786, - 0x225c8, - 0x2aa485, - 0xaa646, - 0x22e585, - 0x26cd44, - 0x3d2747, - 0x2febca, - 0x248108, - 0x32f706, - 0x25a83, - 0x2e1985, - 0x320ac6, - 0x3d6f86, - 0x3338c6, - 0x211d83, - 0x3a4cc7, - 0x208545, - 0x20ce83, - 0x2e014d, - 0x204783, - 0x36ad88, - 0x210504, - 0x279a45, - 0x2a7146, - 0x3981c6, - 0x2a8507, - 0x25c107, - 0x28ba85, - 0x23f7c3, - 0x2e41c7, - 0x310749, - 0x37b709, - 0x32a04a, - 0x23cb02, - 0x20af84, - 0x39a444, - 0x2e9e47, - 0x2ea4c8, - 0x2ec7c9, - 0x20dd09, - 0x2ed507, - 0xfab89, - 0x34b446, - 0xf09c6, - 0x2f2484, - 0x2f2a8a, - 0x2f57c8, - 0x2f7549, - 0x2f7b06, - 0x2b6ec5, - 0x247fc8, - 0x2caf4a, - 0x252c43, - 0x330806, - 0x2ed607, - 0x2aa905, - 0x39c985, - 0x22a8c3, - 0x23bb84, - 0x227345, - 0x287a47, - 0x2f3685, - 0x2f1c46, - 0x1017c5, - 0x289e43, - 0x3d1d89, - 0x27980c, - 0x2b994c, - 0x2d4d48, - 0x2abdc7, - 0x301e48, - 0x102487, - 0x302cca, - 0x30338b, - 0x228cc8, - 0x3982c8, - 0x22a2c6, - 0x28c345, - 0x33db4a, - 0x2cc105, - 0x21a602, - 0x2c8d07, - 0x250e06, - 0x378e85, - 0x3cb209, - 0x2120c5, - 0x31c245, - 0x3be6c9, - 0x320a06, - 0x3c0148, - 0x269f83, - 0x208e06, - 0x278506, - 0x311a45, - 0x311a49, - 0x21de09, - 0x28c0c7, - 0x114944, - 0x314947, - 0x20dc09, - 0x21e845, - 0x3a208, - 0x348a05, - 0x2fb885, - 0x3869c9, - 0x202602, - 0x22cd04, + 0x31fa06, + 0x124804, + 0x38ad87, + 0x22090a, + 0x223989, + 0x3b2c87, + 0x3b588a, + 0x248343, + 0x3ac10b, + 0x3c28c9, + 0x2d3185, + 0x3ae6c7, + 0xc302, + 0x22c0c3, + 0x3c3187, + 0x26a3c5, + 0x2c7249, + 0x232c43, + 0x2bd546, + 0x2c5d83, + 0xcfe03, + 0x115f06, + 0x13f146, + 0xb847, + 0x21e686, + 0x2276c5, + 0x3de0c7, + 0x312847, + 0x69e28b03, + 0x350e47, + 0x3c0043, + 0x20a405, + 0x224e44, + 0x26f848, + 0x37a38c, + 0x2b2045, + 0x2a6746, + 0x3c3047, + 0x3a9ac7, + 0x243a87, + 0x24fc48, + 0x314f4f, + 0x223bc5, + 0x23e587, + 0x205147, + 0x2a850a, + 0x2ec6c9, + 0x31e445, + 0x320fca, + 0xbc7c6, + 0xb9a07, + 0x2c5e05, + 0x2ed104, + 0x3c0146, + 0xdd246, + 0x381d07, + 0x2f0fc7, + 0x369748, + 0x2188c5, + 0x26a2c6, + 0x25788, + 0x2eaf85, + 0xeb146, + 0x2311c5, + 0x28b084, + 0x306907, + 0x238fca, + 0x336408, + 0x36a346, + 0x38cc3, + 0x2e2a45, + 0x322406, + 0x3b5186, + 0x375206, + 0x211543, + 0x3a0b07, + 0x2050c5, + 0x20a803, + 0x2df80d, + 0x20ca43, + 0x369848, + 0x20fcc4, + 0x276f45, + 0x2a8406, + 0x394306, + 0x2a9407, + 0x259d07, + 0x28aa85, + 0x216603, + 0x31a207, + 0x316f89, + 0x26e2c9, + 0x2524ca, + 0x2091c2, + 0x20a3c4, + 0x302604, + 0x2ee247, + 0x2ee648, + 0x2f0889, + 0x3c6649, + 0x2f1507, + 0x101f49, + 0x21ee46, + 0xf4a86, + 0x2f61c4, + 0x22c50a, + 0x2fab08, + 0x2fc809, + 0x2fcdc6, + 0x2b6305, + 0x3362c8, + 0x2cb88a, + 0x24f1c3, + 0x3454c6, + 0x2f1607, + 0x31f785, + 0x3a4245, + 0x240a83, + 0x246484, + 0x228305, + 0x285dc7, + 0x2f90c5, + 0x2f6a46, + 0x11ba45, + 0x359a43, + 0x3d26c9, + 0x276d0c, + 0x2bb5cc, + 0x39e908, + 0x2a98c7, + 0x3085c8, + 0x108c07, + 0x30944a, + 0x309b0b, + 0x3c2a08, + 0x394408, + 0x3db806, + 0x27d485, + 0x33954a, + 0x36d645, + 0x219f82, + 0x2c9ac7, + 0x24d686, + 0x377b45, + 0x30adc9, + 0x27ae85, + 0x295b05, + 0x2f8249, + 0x322346, + 0x329788, + 0x267dc3, + 0x21e7c6, + 0x275506, + 0x318085, + 0x318089, + 0x2bc409, + 0x27d207, + 0x11abc4, + 0x31abc7, + 0x3c6549, + 0x220b05, + 0x37ec8, + 0x342dc5, + 0x28e1c5, + 0x383dc9, + 0x202542, + 0x3d2c04, 0x201e82, 0x201742, - 0x2fb305, - 0x322d88, - 0x2baec5, - 0x2c7543, - 0x2c7545, - 0x2d79c3, - 0x2075c2, - 0x3dba44, - 0x28e843, + 0x2e5285, + 0x324588, + 0x2b9485, + 0x2c8c83, + 0x2c8c85, + 0x2d6f43, + 0x2071c2, + 0x331d04, + 0x26e683, 0x200a82, - 0x3bd804, - 0x2e77c3, - 0x203b02, - 0x2baf43, - 0x2fe504, - 0x2f7c83, - 0x256c84, - 0x205fc2, - 0x214d03, - 0x21af43, - 0x2026c2, - 0x354282, - 0x21dc49, - 0x210242, - 0x28b304, - 0x205e02, - 0x247e44, - 0x34b404, - 0x32ecc4, - 0x204cc2, - 0x229f02, - 0x2dc6c3, - 0x303143, - 0x22e684, - 0x26a004, - 0x2d0384, - 0x2ed784, - 0x314ac3, - 0x245a43, - 0x348e84, - 0x316f44, - 0x317086, - 0x224682, - 0x3102, - 0x41c83, - 0x203102, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, + 0x3b8944, + 0x311943, + 0x212842, + 0x2b9503, + 0x213604, + 0x2fcf43, + 0x254ec4, + 0x204e82, + 0x216003, + 0x219c83, + 0x202602, + 0x293042, + 0x2bc249, + 0x20fa02, + 0x28a304, + 0x20d542, + 0x336144, + 0x21ee04, + 0x252b44, + 0x205402, + 0x23b4c2, + 0x387e43, + 0x298cc3, + 0x2614c4, + 0x28dcc4, + 0x2d0984, + 0x2f1784, + 0x31ad43, + 0x300b03, + 0x2bc744, + 0x31d9c4, + 0x31db06, + 0x20b582, + 0xc302, + 0x3ef83, + 0x20c302, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, 0x2000c2, - 0x24af03, - 0x22f743, - 0x234e83, - 0x2053c3, - 0x224943, - 0x221b84, - 0x21df04, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x214e03, - 0x2f3044, - 0x324003, - 0x2a96c3, - 0x37db04, - 0x348806, - 0x20fc03, - 0x129245, - 0xe747, - 0x26f503, - 0x69a49648, - 0x250603, - 0x2b5943, - 0x20b003, - 0x225a83, - 0x3542c5, - 0x1b2fc3, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x207703, - 0x231283, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x214503, - 0x20ce83, - 0x281184, - 0x71003, - 0x23f7c3, - 0x2b7ec4, - 0x129245, - 0x2c1185, - 0xe747, - 0x203102, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x205e03, + 0x228b03, + 0x224e44, + 0x2bc504, + 0x217b84, + 0x20a803, + 0x216603, + 0x216103, + 0x2f8a84, + 0x32ce03, + 0x2aad03, + 0x37a944, + 0x342bc6, + 0x20e5c3, + 0x12eb85, + 0x176c87, + 0x2e4003, + 0x6b644548, + 0x2420c3, + 0x2b4103, + 0x20a443, + 0x238cc3, + 0x3afd05, + 0x1ae683, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x208243, + 0x22dcc3, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x214543, + 0x20a803, + 0x27f984, + 0x6d9c3, + 0x216603, + 0x2ba0c4, + 0x12eb85, + 0x2c2ac5, + 0x176c87, + 0x20c302, 0x201d02, 0x200382, - 0x208ac2, - 0x4783, + 0x205642, + 0xca43, 0x2003c2, 0x1244, - 0x22f743, - 0x2375c4, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x209c04, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0x202443, - 0x250784, - 0xa7c88, - 0x22f743, - 0x204783, - 0xd103, - 0x14cfc4, - 0x23d544, - 0xa7c88, - 0x22f743, - 0x24f044, - 0x221b84, - 0x204783, + 0x22c0c3, + 0x235604, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x217b84, + 0x20a803, + 0xca43, + 0x216603, + 0x20c603, + 0x242244, + 0x9fe08, + 0x22c0c3, + 0x20ca43, + 0x8303, + 0x123ec4, + 0x249f84, + 0x9fe08, + 0x22c0c3, + 0x24b304, + 0x224e44, + 0x20ca43, 0x201e02, - 0x71003, - 0x23f7c3, - 0x259703, - 0x3bb84, - 0x207ac5, - 0x21a602, - 0x376243, - 0x127409, - 0xde2c6, - 0x148b08, + 0x6d9c3, + 0x216603, + 0x236bc3, + 0x46484, + 0x204185, + 0x219f82, + 0x2be683, + 0x2b49, + 0xddb86, + 0x142ec8, 0x2000c2, - 0xa7c88, - 0x203102, - 0x234e83, - 0x224943, + 0x9fe08, + 0x20c302, + 0x232c43, + 0x228b03, 0x2005c2, - 0x4783, - 0x23f7c3, - 0x4f02, + 0xca43, + 0x216603, + 0x5942, 0x82, + 0xc2, + 0x1b5a47, + 0x13dc09, + 0x7be83, + 0x9fe08, + 0x27643, + 0x6ef26287, + 0x2c0c3, + 0x6048, + 0x32c43, + 0x28b03, + 0x3a086, + 0x14543, + 0x96448, + 0xc53c8, + 0x79046, + 0x11543, + 0xce788, + 0xb7e03, + 0x6f0e23c6, + 0xea185, + 0x32e47, + 0xa803, + 0x21803, + 0x16603, + 0xb142, + 0x17d48a, + 0x4e03, + 0xe5343, + 0xfe804, + 0x114d4b, + 0x115308, + 0x91482, + 0x1451207, + 0x153efc7, + 0x14c8d48, + 0x151d403, + 0x10044b, + 0x8582, + 0x12ea07, + 0x10cbc4, 0x2000c2, - 0x1b8b07, - 0x142149, - 0x7d6c3, - 0xa7c88, - 0x1c303, - 0x6d34f3c7, - 0x2f743, - 0x1c2908, - 0x234e83, - 0x224943, - 0x3fb86, - 0x214503, - 0x96c08, - 0xc3a48, - 0x116206, - 0x211d83, - 0xcdf88, - 0xbacc3, - 0x6d4e1306, - 0xe5285, - 0x35087, - 0xce83, - 0x444c3, - 0x3f7c3, - 0xda42, - 0x19fc4a, - 0x8283, - 0xfb3c3, - 0x2fca44, - 0x10dacb, - 0x10e088, - 0x91f82, - 0x1455187, - 0x152e787, - 0x14c7608, - 0x1515883, - 0x12708b, - 0xba42, - 0x125c07, - 0x1069c4, - 0x2000c2, - 0x203102, - 0x2375c4, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x225a83, - 0x20ce83, - 0x23f7c3, - 0x228743, - 0x202443, - 0x31e43, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, + 0x20c302, + 0x235604, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x238cc3, + 0x20a803, + 0x216603, + 0x2ba0c3, + 0x20c603, + 0x2f783, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, 0x602, - 0xd103, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x225a83, - 0x20ce83, - 0x23f7c3, - 0x210782, + 0x8303, + 0x28b03, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x238cc3, + 0x20a803, + 0x216603, + 0x20ff42, 0x2000c1, 0x2000c2, 0x200201, - 0x33b542, - 0xa7c88, - 0x21c785, + 0x336f42, + 0x9fe08, + 0x21c105, 0x200101, - 0x2f743, - 0x32244, + 0x2c0c3, + 0x2fd84, 0x2015c1, 0x200501, 0x2014c1, - 0x249302, - 0x38b104, - 0x249303, + 0x244202, + 0x3874c4, + 0x244203, 0x200041, 0x200801, 0x200181, 0x200701, - 0x3535c7, - 0x31e5cf, - 0x30eec6, + 0x2f6b87, + 0x380f4f, + 0x3cac46, 0x2004c1, - 0x34d006, + 0x323f06, 0x200bc1, 0x200581, - 0x3d854e, + 0x3d878e, 0x2003c1, - 0x23f7c3, + 0x216603, 0x200a81, - 0x22d185, - 0x20da42, - 0x22a7c5, + 0x2e3285, + 0x20b142, + 0x240985, 0x200401, 0x200741, 0x2007c1, - 0x21a602, + 0x219f82, 0x200081, - 0x202d01, + 0x205841, 0x201241, 0x2018c1, - 0x2086c1, - 0x52449, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x2158c3, - 0x22f743, - 0x224943, - 0x91ec8, - 0x211d83, - 0x20ce83, - 0x71283, - 0x23f7c3, - 0x14e8c08, - 0x7608, - 0x129245, - 0xa7c88, - 0x4783, - 0x129245, - 0x45344, - 0x40f88, - 0x46cc4, - 0xbd485, - 0x52449, - 0x14e8c0a, - 0xa7c88, - 0x71003, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x21c183, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x2dbb04, - 0x23f7c3, - 0x266f85, - 0x316504, - 0x22f743, - 0x234e83, - 0x224943, - 0x205982, - 0x20ce83, - 0x23f7c3, - 0x2443, - 0xa834a, - 0x113284, - 0x119f46, - 0x24af03, - 0x22f743, - 0x234e83, - 0x224943, - 0x20ce83, - 0x23f7c3, - 0x203102, - 0x22f743, - 0x2327c9, - 0x234e83, - 0x2aca09, - 0x224943, - 0x211d83, - 0x20ce83, - 0x18cfc4, - 0x4783, - 0x23f7c3, - 0x2f2288, - 0x230287, - 0x207ac5, - 0x1d0c08, - 0x1b8b07, - 0xea74a, - 0x71acb, - 0x14d247, - 0x40148, - 0x138a8a, - 0x1cf188, - 0x142149, - 0x27207, - 0x38887, - 0x127d48, - 0x1c2908, - 0x4154f, - 0x16705, - 0x190347, - 0x3fb86, - 0x44407, - 0x119c06, - 0x96c08, - 0x9f046, - 0x120807, - 0x121409, - 0x1c0607, - 0xb2689, - 0xbbc09, - 0xc0f06, - 0xc3a48, - 0xc2385, - 0x7ba8a, - 0xcdf88, - 0xbacc3, - 0xd8d08, - 0x35087, - 0x16c105, - 0x7f950, - 0x444c3, - 0x71003, - 0x121287, - 0x196c5, - 0xebe48, - 0x67705, - 0xfb3c3, - 0x176b88, - 0x1c67c6, - 0x1ae489, - 0xaec07, - 0x1276cb, - 0x701c4, - 0x104e04, - 0x10dacb, - 0x10e088, - 0x10ec87, - 0x129245, - 0x22f743, - 0x234e83, - 0x20d343, - 0x23f7c3, - 0x23f343, - 0x224943, - 0x71003, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x8234b, + 0x204981, + 0x4e9c9, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x216e03, + 0x22c0c3, + 0x228b03, + 0x913c8, + 0x211543, + 0x20a803, + 0x70e03, + 0x216603, + 0x14ecd48, + 0x8148, + 0x12eb85, + 0x9fe08, + 0xca43, + 0x12eb85, + 0x1da144, + 0x116c8, + 0x42744, + 0xc9345, + 0x4e9c9, + 0x14ecd4a, + 0x9fe08, + 0x6d9c3, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x225843, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x2db1c4, + 0x216603, + 0x25cf45, + 0x27ac84, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x206a42, + 0x20a803, + 0x216603, + 0xc603, + 0xa924a, + 0x119b84, + 0x121d46, + 0x248343, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x20a803, + 0x216603, + 0x20c302, + 0x22c0c3, + 0x230309, + 0x232c43, + 0x2ac809, + 0x228b03, + 0x211543, + 0x20a803, + 0x189a44, + 0xca43, + 0x216603, + 0x2f5fc8, + 0x23ad87, + 0x204185, + 0x1d1548, + 0x1b5a47, + 0xee8ca, + 0x6fe0b, + 0x124147, + 0x3cd48, + 0x1198a, + 0x1a348, + 0x13dc09, + 0x281c7, + 0x106707, + 0x140cc8, + 0x6048, + 0x3e84f, + 0x17c45, + 0x16687, + 0x3a086, + 0x3fc07, + 0x11e586, + 0x96448, + 0x9f546, + 0x129587, + 0x143489, + 0x1a4ec7, + 0x9be49, + 0xba9c9, + 0xc2846, + 0xc53c8, + 0xc3a05, + 0x7c70a, + 0xce788, + 0xb7e03, + 0xd7348, + 0x32e47, + 0x13e8c5, + 0x64910, + 0x21803, + 0x6d9c3, + 0x129407, + 0x231c5, + 0xeff88, + 0x65305, + 0xe5343, + 0x3308, + 0xb446, + 0x92289, + 0xaebc7, + 0x2e0b, + 0x6c1c4, + 0x10b8c4, + 0x114d4b, + 0x115308, + 0x115e07, + 0x12eb85, + 0x22c0c3, + 0x232c43, + 0x212483, + 0x216603, + 0x23bf03, + 0x228b03, + 0x6d9c3, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x1988cb, 0x2000c2, - 0x203102, - 0x23f7c3, - 0xa7c88, - 0x3102, + 0x20c302, + 0x216603, + 0x9fe08, + 0x133d89, + 0xc302, 0x2000c2, - 0x203102, + 0x20c302, 0x200382, 0x2005c2, - 0x204802, - 0x20ce83, - 0x135706, + 0x206702, + 0x20a803, + 0x133a46, 0x2003c2, - 0x3bb84, + 0x46484, 0x2000c2, - 0x24af03, - 0x203102, - 0x22f743, - 0x234e83, + 0x248343, + 0x20c302, + 0x22c0c3, + 0x232c43, 0x200382, - 0x224943, - 0x214503, - 0x211d83, - 0x209c04, - 0x20ce83, - 0x2130c3, - 0x4783, - 0x23f7c3, - 0x2fca44, - 0x2050c3, - 0x224943, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x204783, - 0x23f7c3, - 0x3bd0c7, - 0x22f743, - 0x27d8c7, - 0x35df46, - 0x20a8c3, - 0x2143c3, - 0x224943, - 0x2083c3, - 0x221b84, - 0x39b5c4, - 0x30f746, - 0x202143, - 0x20ce83, - 0x23f7c3, - 0x266f85, - 0x3283c4, - 0x34fb43, - 0x2c6703, - 0x2c8d07, - 0x2c5f05, - 0x22f743, - 0x234e83, - 0x224943, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x772fc18c, - 0x4ddc7, - 0xdd286, - 0x930c7, - 0x1a29c5, - 0x206c82, - 0x38ffc3, - 0x206203, - 0x24af03, - 0x77e2f743, - 0x2037c2, - 0x234e83, + 0x228b03, + 0x214543, + 0x211543, + 0x217b84, + 0x20a803, + 0x213dc3, + 0xca43, + 0x216603, + 0x2fe804, + 0x205b03, + 0x228b03, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x20ca43, + 0x216603, + 0x3b8207, + 0x22c0c3, + 0x27c087, + 0x35f6c6, + 0x216b03, + 0x214403, + 0x228b03, + 0x204f43, + 0x224e44, + 0x300bc4, + 0x3187c6, + 0x218f83, + 0x20a803, + 0x216603, + 0x25cf45, + 0x34f1c4, + 0x326a03, + 0x276683, + 0x2c9ac7, + 0x20f805, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x211543, + 0x20a803, + 0x216603, + 0x14803, + 0x7970270c, + 0x50e87, + 0xbe846, + 0x114587, + 0x8f6c5, + 0x20be02, + 0x245a83, + 0x208b83, + 0x248343, + 0x7a22c0c3, + 0x207902, + 0x232c43, 0x2020c3, - 0x224943, - 0x221b84, + 0x228b03, + 0x224e44, 0x201143, - 0x21a0c3, - 0x211d83, - 0x209c04, - 0x78205682, - 0x20ce83, - 0x23f7c3, - 0x209983, - 0x222f03, - 0x20cf03, - 0x210782, - 0x2050c3, - 0xa7c88, - 0x224943, - 0xd103, - 0x21f004, - 0x24af03, - 0x203102, - 0x22f743, - 0x2375c4, - 0x234e83, - 0x224943, - 0x221b84, - 0x214503, - 0x3bac04, - 0x346484, - 0x2da646, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x214e03, - 0x250e06, - 0x36b8b, - 0x25786, - 0x6da0a, - 0x112d0a, - 0xa7c88, - 0x222584, - 0x7962f743, - 0x320c84, - 0x234e83, - 0x2d7004, - 0x224943, - 0x203843, - 0x211d83, - 0x20ce83, - 0x71003, - 0x23f7c3, - 0xa1c3, - 0x349f4b, - 0x3c8bca, - 0x3da28c, - 0xe1708, + 0x223bc3, + 0x211543, + 0x217b84, + 0x7a612b02, + 0x20a803, + 0x216603, + 0x21d0c3, + 0x22ce03, + 0x20a883, + 0x20ff42, + 0x205b03, + 0x9fe08, + 0x228b03, + 0x8303, + 0x322c44, + 0x248343, + 0x20c302, + 0x22c0c3, + 0x235604, + 0x232c43, + 0x228b03, + 0x224e44, + 0x214543, + 0x246b84, + 0x303f84, + 0x2d9bc6, + 0x217b84, + 0x20a803, + 0x216603, + 0x216103, + 0x24d686, + 0x3494b, + 0x39346, + 0x396ca, + 0x11960a, + 0x9fe08, + 0x225744, + 0x7ba2c0c3, + 0x3db044, + 0x232c43, + 0x26e144, + 0x228b03, + 0x220383, + 0x211543, + 0x20a803, + 0x6d9c3, + 0x216603, + 0x47203, + 0x34b00b, + 0x3c840a, + 0x3dc60c, + 0xe27c8, 0x2000c2, - 0x203102, + 0x20c302, 0x200382, - 0x230145, - 0x221b84, - 0x205982, - 0x211d83, - 0x346484, - 0x208ac2, + 0x22cd85, + 0x224e44, + 0x206a42, + 0x211543, + 0x303f84, + 0x205642, 0x2003c2, - 0x202442, - 0x210782, - 0x4af03, - 0x47502, - 0x2be789, - 0x340e48, - 0x2247c9, - 0x21f909, - 0x2ab2ca, - 0x318c0a, - 0x214c42, - 0x2291c2, - 0x3102, - 0x22f743, - 0x22ca82, - 0x241446, - 0x37a202, + 0x209482, + 0x20ff42, + 0x48343, + 0x9382, + 0x2c4009, + 0x364448, + 0x228989, + 0x208649, + 0x2181ca, + 0x22170a, + 0x203cc2, + 0x28cac2, + 0xc302, + 0x22c0c3, + 0x22ae02, + 0x23e746, + 0x378f82, 0x201682, - 0x271e4e, - 0x214d4e, - 0x281447, - 0x20ce07, - 0x24f302, - 0x234e83, - 0x224943, - 0x207282, + 0x27018e, + 0x21604e, + 0x27fc47, + 0x20a787, + 0x24b5c2, + 0x232c43, + 0x228b03, + 0x20d602, 0x2005c2, - 0x14343, - 0x2377cf, - 0x238902, - 0x2b4407, - 0x36e087, - 0x2b6a07, - 0x2d198c, - 0x2d26cc, - 0x21ecc4, - 0x39f08a, - 0x214c82, - 0x251b42, - 0x2bd784, + 0x14383, + 0x23580f, + 0x23ea82, + 0x366f87, + 0x2b1bc7, + 0x354207, + 0x2b590c, + 0x2e09cc, + 0x3d0384, + 0x39b8ca, + 0x211902, + 0x24e082, + 0x2bce04, 0x200702, - 0x2b0602, - 0x2d2904, - 0x2131c2, - 0x20e942, - 0xe943, - 0x29f0c7, - 0x23a9c5, - 0x2065c2, - 0x244384, - 0x327e02, - 0x2e1488, - 0x20ce83, - 0x379208, - 0x201fc2, - 0x21ee85, - 0x398ac6, - 0x23f7c3, - 0x207702, - 0x2eca07, - 0xda42, - 0x3a4905, - 0x321185, - 0x205d02, - 0x20ba82, - 0x2097ca, - 0x28b90a, - 0x287542, - 0x29fc44, - 0x205202, - 0x20ae48, - 0x208c02, - 0x301148, - 0x309407, - 0x30a189, - 0x2e5d02, - 0x30ffc5, - 0x36d845, - 0x2183cb, - 0x30fccc, - 0x22c588, - 0x325a48, - 0x224682, - 0x2a85c2, - 0x2000c2, - 0xa7c88, + 0x2c4fc2, + 0x2e0c04, + 0x213ec2, 0x203102, - 0x22f743, - 0x200382, - 0x208ac2, - 0x4783, - 0x2003c2, - 0x23f7c3, - 0x202442, + 0xe403, + 0x29f5c7, + 0x238685, + 0x217002, + 0x23fb84, + 0x340d82, + 0x2e2548, + 0x20a803, + 0x377ec8, + 0x201fc2, + 0x3d0545, + 0x394d46, + 0x216603, + 0x208242, + 0x2f0ac7, + 0xb142, + 0x212ec5, + 0x301185, + 0x216442, + 0x2085c2, + 0x21cf0a, + 0x28a90a, + 0x287582, + 0x2a0cc4, + 0x205c42, + 0x20a288, + 0x205782, + 0x356708, + 0xf01, + 0x30ef47, + 0x310a49, + 0x212f42, + 0x316805, + 0x3b0205, + 0x21898b, + 0x318d4c, + 0x22a908, + 0x32e848, + 0x20b582, + 0x2a94c2, 0x2000c2, - 0x129245, - 0x7aa03102, - 0x7b224943, - 0x20e943, - 0x205982, - 0x20ce83, - 0x3bb643, - 0x7b63f7c3, - 0x2e9343, - 0x283206, - 0x1602443, - 0x129245, - 0x1355cb, - 0xa7c88, - 0x7ae8c588, - 0x895c7, - 0x70887, - 0x1339c5, - 0x1e00d, - 0x3f982, - 0x10e682, - 0xa910a, - 0x8b747, - 0x1bfc4, - 0x1c003, - 0x1b9084, - 0x7be048c2, - 0x7c200ac2, - 0x7c603002, - 0x7ca04182, - 0x7ce09fc2, - 0x7d207982, - 0xe747, - 0x7d603102, - 0x7da31082, - 0x7de1f642, - 0x7e207682, - 0x214d43, - 0x12984, - 0x239043, - 0x7e60e382, - 0x5ba48, - 0x7ea02ec2, - 0x4f947, - 0x7ee00042, - 0x7f200d82, - 0x7f600182, - 0x7fa03842, - 0x7fe00f42, - 0x802005c2, - 0xd6585, - 0x24f543, - 0x338544, - 0x80600702, - 0x80a01882, - 0x80e04b42, - 0x7c68b, - 0x81200c42, - 0x81a49602, - 0x81e05982, - 0x82204802, - 0x8261f382, - 0x82a00bc2, - 0x82e04fc2, - 0x8326ff42, - 0x83605682, - 0x83a04bc2, - 0x83e08ac2, - 0x84216e02, - 0x8463d2c2, - 0x84a24a82, - 0xb40c4, - 0x2163c3, - 0x84e00ec2, - 0x85210482, - 0x85602f82, - 0x85a006c2, - 0x85e003c2, - 0x86200a82, - 0x824c7, - 0x86614e02, - 0x86a023c2, - 0x86e02442, - 0x87214d02, - 0x1b030c, - 0x87644d02, - 0x87a20302, - 0x87e02742, - 0x882038c2, - 0x88600f02, - 0x88a77dc2, - 0x88e02d02, - 0x8921c902, - 0x89678882, - 0x89a79482, - 0x247502, + 0x9fe08, + 0x20c302, + 0x22c0c3, + 0x200382, + 0x205642, + 0xca43, + 0x2003c2, + 0x216603, + 0x209482, + 0x2000c2, + 0x12eb85, + 0x7ce0c302, + 0x7d628b03, + 0x20e403, + 0x206a42, + 0x20a803, + 0x3559c3, + 0x7da16603, + 0x2ed343, + 0x281ac6, + 0x160c603, + 0x12eb85, + 0x13390b, + 0x9fe08, + 0x7d27d6c8, + 0x7e407, + 0x6d247, + 0x175305, + 0x2a20d, + 0x39e82, + 0x115902, + 0xaa74a, + 0x8a747, + 0x27304, + 0x27343, + 0x1bffc4, + 0x7e204ec2, + 0x7e600ac2, + 0x7ea02282, + 0x7ee03342, + 0x7f209bc2, + 0x7f604042, + 0x176c87, + 0x7fa0c302, + 0x7fe2dac2, + 0x80221442, + 0x806081c2, + 0x216043, + 0x12704, + 0x236cc3, + 0x80a0c782, + 0x59648, + 0x80e076c2, + 0x4bc07, + 0x81200042, + 0x81600d82, + 0x81a00182, + 0x81e03dc2, + 0x82200f42, + 0x826005c2, + 0xd30c5, + 0x215183, + 0x36c284, + 0x82a00702, + 0x82e01882, + 0x83203c42, + 0x86ccb, + 0x83600c42, + 0x83e44502, + 0x84206a42, + 0x84606702, + 0x84a1d902, + 0x84e00bc2, + 0x85205a02, + 0x8566bf42, + 0x85a12b02, + 0x85e04f82, + 0x86205642, + 0x86636002, + 0x86a6f802, + 0x86e28c42, + 0x194584, + 0x217903, + 0x87200ec2, + 0x8760fc42, + 0x87a0ad82, + 0x87e006c2, + 0x882003c2, + 0x88600a82, + 0x198a47, + 0x88a16102, + 0x88e03d82, + 0x89209482, + 0x89616002, + 0x1a214c, + 0x89a47c82, + 0x89e22182, + 0x8a202682, + 0x8a617042, + 0x8aa00f02, + 0x8ae18342, + 0x8b205842, + 0x8b60b902, + 0x8ba75882, + 0x8be369c2, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x15cc3, - 0x247502, + 0x17203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, - 0x81601143, - 0x215cc3, - 0x354344, - 0x2246c6, - 0x2f8f83, - 0x247502, + 0x217203, + 0x209382, + 0x83a01143, + 0x217203, + 0x3afd84, + 0x228886, + 0x2fd643, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x3758c9, - 0x247502, - 0x3d31c3, - 0x2bbf03, - 0x32fdc5, + 0x217203, + 0x2bdd09, + 0x209382, + 0x39c403, + 0x2bacc3, + 0x36aa05, 0x2020c3, 0x201143, - 0x215cc3, - 0x2a2c03, - 0x229143, - 0x23c689, - 0x247502, + 0x217203, + 0x28f543, + 0x221a43, + 0x34d8c9, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, + 0x217203, + 0x209382, 0x201143, - 0x215cc3, - 0x247502, - 0x247502, + 0x217203, + 0x209382, + 0x209382, 0x201143, - 0x215cc3, - 0x8a22f743, - 0x234e83, - 0x21fb43, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, - 0xa7c88, - 0x203102, - 0x22f743, - 0x20ce83, - 0x23f7c3, - 0x22f743, - 0x234e83, - 0x224943, - 0x8ace63c2, - 0x211d83, - 0x20ce83, - 0x4783, - 0x23f7c3, + 0x217203, + 0x8c62c0c3, + 0x232c43, + 0x208883, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, + 0x9fe08, + 0x20c302, + 0x22c0c3, + 0x20a803, + 0x216603, + 0xbdb82, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x8d0ebe82, + 0x211543, + 0x20a803, + 0xca43, + 0x216603, 0x15c1, - 0x23d544, - 0x203102, - 0x22f743, + 0x249f84, + 0x20c302, + 0x22c0c3, 0x200983, - 0x234e83, - 0x24f044, - 0x20d343, - 0x224943, - 0x221b84, - 0x214503, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x259703, - 0x207ac5, - 0x229143, - 0x2050c3, - 0x4783, - 0x203102, - 0x22f743, + 0x232c43, + 0x24b304, + 0x212483, + 0x228b03, + 0x224e44, + 0x214543, + 0x211543, + 0x20a803, + 0x216603, + 0x236bc3, + 0x204185, + 0x221a43, + 0x205b03, + 0xca43, + 0x20c302, + 0x22c0c3, 0x201143, - 0x20ce83, - 0x23f7c3, + 0x20a803, + 0x216603, 0x2000c2, - 0x24af03, - 0xa7c88, - 0x22f743, - 0x234e83, - 0x224943, - 0x231b06, - 0x221b84, - 0x214503, - 0x209c04, - 0x20ce83, - 0x23f7c3, - 0x214e03, - 0x22f743, - 0x234e83, - 0x20ce83, - 0x23f7c3, - 0x28002, + 0x248343, + 0x9fe08, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x22f446, + 0x224e44, + 0x214543, + 0x217b84, + 0x20a803, + 0x216603, + 0x216103, + 0x22c0c3, + 0x232c43, + 0x20a803, + 0x216603, + 0x2d7c2, 0x1942, - 0x144e8c7, - 0x56107, - 0x22f743, - 0x25786, - 0x234e83, - 0x224943, - 0xe2cc6, - 0x20ce83, - 0x23f7c3, - 0x322548, - 0x325889, - 0x342f49, - 0x34c4c8, - 0x39abc8, - 0x39abc9, - 0x31ac4a, - 0x35e24a, - 0x395f8a, - 0x39c18a, - 0x3c8bca, - 0x3d588b, - 0x24638d, - 0x368e4f, - 0x276490, - 0x3605cd, - 0x3813cc, - 0x39becb, - 0x70a88, - 0xe6208, - 0x166745, - 0x14891c7, - 0xcc705, + 0x1458787, + 0x141347, + 0x22c0c3, + 0x39346, + 0x232c43, + 0x228b03, + 0xe7d46, + 0x20a803, + 0x216603, + 0x32b688, + 0x32e689, + 0x341509, + 0x34cd48, + 0x396f48, + 0x396f49, + 0x32370a, + 0x35f9ca, + 0x39278a, + 0x39914a, + 0x3c840a, + 0x3d5fcb, + 0x23d20d, + 0x367bcf, + 0x25b050, + 0x36144d, + 0x37e04c, + 0x398e8b, + 0x6d448, + 0xebcc8, + 0x92e85, + 0x1488147, + 0xccc85, 0x2000c2, - 0x2c5d45, - 0x20e903, - 0x8e203102, - 0x234e83, - 0x224943, - 0x391807, - 0x20b003, - 0x211d83, - 0x20ce83, - 0x22c483, - 0x2130c3, - 0x20b3c3, - 0x204783, - 0x23f7c3, - 0x254c06, - 0x21a602, - 0x2050c3, - 0xa7c88, + 0x20f645, + 0x20e3c3, + 0x9060c302, + 0x232c43, + 0x228b03, + 0x3d4007, + 0x20a443, + 0x211543, + 0x20a803, + 0x24bbc3, + 0x213dc3, + 0x209a83, + 0x20ca43, + 0x216603, + 0x253c46, + 0x219f82, + 0x205b03, + 0x9fe08, 0x2000c2, - 0x24af03, - 0x203102, - 0x22f743, - 0x234e83, - 0x224943, - 0x221b84, - 0x211d83, - 0x20ce83, - 0x23f7c3, - 0x202443, - 0x56107, - 0xba42, - 0x127404, - 0x15c8f86, + 0x248343, + 0x20c302, + 0x22c0c3, + 0x232c43, + 0x228b03, + 0x224e44, + 0x211543, + 0x20a803, + 0x216603, + 0x20c603, + 0x141347, + 0x8582, + 0x2b44, + 0x15c87c6, 0x2000c2, - 0x203102, - 0x224943, - 0x211d83, - 0x23f7c3, + 0x20c302, + 0x228b03, + 0x211543, + 0x216603, } // children is the list of nodes' children, the parent's wildcard bit and the @@ -9427,576 +9462,585 @@ var children = [...]uint32{ 0x40000000, 0x50000000, 0x60000000, - 0x1820602, - 0x1824608, - 0x1828609, - 0x184c60a, - 0x19a8613, - 0x19c066a, - 0x19d4670, - 0x19ec675, - 0x1a0c67b, - 0x1a24683, - 0x1a3c689, - 0x1a5468f, - 0x1a58695, - 0x1a80696, - 0x1a846a0, - 0x1a9c6a1, - 0x1aa06a7, - 0x1aa46a8, - 0x1ae06a9, - 0x1ae46b8, - 0x61aec6b9, - 0x21af46bb, - 0x1b3c6bd, + 0x17f85f8, + 0x17fc5fe, + 0x18005ff, + 0x1824600, + 0x1980609, + 0x1998660, + 0x19ac666, + 0x19c466b, + 0x19e4671, + 0x19fc679, + 0x1a1467f, + 0x1a2c685, + 0x1a3068b, + 0x1a5868c, + 0x1a5c696, + 0x1a74697, + 0x1a7869d, + 0x1a7c69e, + 0x1ab869f, + 0x1abc6ae, + 0x61ac46af, + 0x21acc6b1, + 0x1b146b3, + 0x1b186c5, + 0x1b3c6c6, 0x1b406cf, - 0x1b686d0, - 0x1b6c6da, - 0x1b706db, - 0x1b846dc, - 0x1b886e1, - 0x1bb86e2, - 0x1bd46ee, - 0x1bfc6f5, - 0x1c0c6ff, - 0x1c10703, - 0x1ca8704, - 0x1cbc72a, - 0x1cd072f, - 0x1d08734, - 0x1d18742, - 0x1d2c746, - 0x1d4474b, - 0x1de8751, - 0x1fec77a, - 0x1ff07fb, - 0x205c7fc, - 0x20c8817, - 0x20e0832, - 0x20f4838, - 0x20f883d, - 0x210083e, - 0x2114840, - 0x2118845, - 0x2134846, - 0x218484d, - 0x2188861, - 0x2218c862, - 0x21a8863, - 0x21ac86a, - 0x21b086b, - 0x21d486c, - 0x2214875, - 0x2218885, - 0x6221c886, - 0x2234887, - 0x225488d, - 0x2260895, - 0x2270898, - 0x232489c, - 0x23288c9, - 0x223388ca, - 0x2233c8ce, - 0x223448cf, - 0x239c8d1, - 0x23a08e7, - 0x28948e8, - 0x2293ca25, + 0x1b446d0, + 0x1b586d1, + 0x1b5c6d6, + 0x1b8c6d7, + 0x1ba86e3, + 0x1bd06ea, + 0x1be06f4, + 0x1be46f8, + 0x1c7c6f9, + 0x1c9071f, + 0x1ca4724, + 0x1cdc729, + 0x1cec737, + 0x1d0073b, + 0x1d18740, + 0x1dbc746, + 0x1fc076f, + 0x1fc47f0, + 0x20307f1, + 0x209c80c, + 0x20b4827, + 0x20c882d, + 0x20cc832, + 0x20d4833, + 0x20e8835, + 0x20ec83a, + 0x210883b, + 0x2158842, + 0x215c856, + 0x22160857, + 0x217c858, + 0x218085f, + 0x2184860, + 0x21a8861, + 0x21e886a, + 0x21ec87a, + 0x621f087b, + 0x220887c, + 0x222c882, + 0x223888b, + 0x224888e, + 0x22fc892, + 0x23008bf, + 0x223108c0, + 0x223148c4, + 0x2231c8c5, + 0x23748c7, + 0x23788dd, + 0x288c8de, + 0x2890a23, + 0x22938a24, + 0x2293ca4e, 0x22940a4f, - 0x22944a50, - 0x22950a51, - 0x22954a54, - 0x22960a55, + 0x2294ca50, + 0x22950a53, + 0x2295ca54, + 0x22960a57, 0x22964a58, 0x22968a59, 0x2296ca5a, 0x22970a5b, - 0x22974a5c, - 0x22980a5d, - 0x22984a60, - 0x22990a61, + 0x2297ca5c, + 0x22980a5f, + 0x2298ca60, + 0x22990a63, 0x22994a64, 0x22998a65, - 0x2299ca66, - 0x229a8a67, - 0x229aca6a, - 0x229b8a6b, + 0x229a4a66, + 0x229a8a69, + 0x229b4a6a, + 0x229b8a6d, 0x229bca6e, 0x229c0a6f, - 0x229c4a70, - 0x29c8a71, - 0x229cca72, - 0x229d8a73, - 0x229dca76, + 0x29c4a70, + 0x229c8a71, + 0x229d4a72, + 0x229d8a75, + 0x29dca76, 0x29e4a77, - 0x2a28a79, - 0x22a48a8a, - 0x22a4ca92, - 0x22a50a93, - 0x22a58a94, + 0x629f0a79, + 0x2a34a7c, + 0x22a54a8d, + 0x22a58a95, 0x22a5ca96, - 0x2a60a97, - 0x22a64a98, + 0x22a64a97, 0x22a68a99, - 0x22a6ca9a, - 0x2a74a9b, - 0x2a78a9d, - 0x2a7ca9e, - 0x2a98a9f, - 0x2ab0aa6, - 0x2ab4aac, - 0x2ac4aad, - 0x2ad0ab1, - 0x2b04ab4, - 0x2b08ac1, - 0x2b20ac2, - 0x22b28ac8, - 0x22b2caca, - 0x22b34acb, - 0x2c24acd, - 0x22c28b09, - 0x2c30b0a, - 0x2c34b0c, - 0x22c38b0d, - 0x2c3cb0e, - 0x2c54b0f, + 0x2a6ca9a, + 0x22a70a9b, + 0x22a74a9c, + 0x22a78a9d, + 0x22a7ca9e, + 0x2a84a9f, + 0x2a8caa1, + 0x2a90aa3, + 0x2aacaa4, + 0x2ac4aab, + 0x2ac8ab1, + 0x2ad8ab2, + 0x2ae4ab6, + 0x2b18ab9, + 0x2b1cac6, + 0x2b34ac7, + 0x22b3cacd, + 0x22b40acf, + 0x22b48ad0, + 0x2c40ad2, + 0x22c44b10, + 0x2c4cb11, + 0x2c50b13, + 0x22c54b14, 0x2c58b15, - 0x2c5cb16, - 0x2c60b17, - 0x2c78b18, - 0x2c8cb1e, - 0x2cb4b23, - 0x2cd4b2d, - 0x2cd8b35, - 0x62cdcb36, - 0x2d10b37, - 0x2d14b44, - 0x22d18b45, - 0x2d1cb46, - 0x2d44b47, - 0x2d48b51, - 0x2d6cb52, - 0x2d70b5b, - 0x2d84b5c, - 0x2d88b61, + 0x2c70b16, + 0x2c74b1c, + 0x2c78b1d, + 0x2c7cb1e, + 0x2c94b1f, + 0x2ca8b25, + 0x2cd0b2a, + 0x2cf0b34, + 0x2cf4b3c, + 0x62cf8b3d, + 0x2d2cb3e, + 0x2d30b4b, + 0x22d34b4c, + 0x2d38b4d, + 0x2d60b4e, + 0x2d64b58, + 0x2d88b59, 0x2d8cb62, - 0x2dacb63, - 0x2dc8b6b, - 0x2dccb72, - 0x22dd0b73, - 0x2dd4b74, - 0x2dd8b75, - 0x2ddcb76, - 0x2de4b77, - 0x2df8b79, - 0x2dfcb7e, - 0x2e00b7f, - 0x2e04b80, - 0x2e74b81, - 0x2e78b9d, - 0x2e7cb9e, - 0x2e9cb9f, - 0x2eb0ba7, - 0x2ec4bac, - 0x2edcbb1, - 0x2ef8bb7, - 0x2f10bbe, - 0x2f14bc4, - 0x2f2cbc5, - 0x2f48bcb, - 0x2f4cbd2, - 0x2f6cbd3, - 0x2f8cbdb, - 0x2fa8be3, - 0x300cbea, - 0x3028c03, - 0x3038c0a, - 0x303cc0e, - 0x3054c0f, - 0x3098c15, - 0x3118c26, - 0x3148c46, - 0x314cc52, - 0x3158c53, - 0x3178c56, - 0x317cc5e, - 0x31a0c5f, - 0x31a8c68, - 0x31e4c6a, - 0x3234c79, - 0x3238c8d, - 0x323cc8e, - 0x3304c8f, - 0x23308cc1, - 0x2330ccc2, - 0x3310cc3, - 0x23314cc4, - 0x23318cc5, - 0x2331ccc6, - 0x2332ccc7, - 0x23330ccb, - 0x23334ccc, - 0x23338ccd, - 0x2333ccce, - 0x3354ccf, - 0x3378cd5, - 0x3398cde, - 0x3a04ce6, - 0x3a10e81, - 0x3a30e84, - 0x3bf0e8c, - 0x3cc0efc, - 0x3d30f30, - 0x3d88f4c, - 0x3e70f62, - 0x3ec8f9c, - 0x3f04fb2, - 0x4000fc1, - 0x40cd000, - 0x4165033, - 0x41f5059, - 0x425907d, - 0x4491096, - 0x4549124, - 0x4615152, - 0x4661185, - 0x46e9198, - 0x47251ba, - 0x47751c9, - 0x47ed1dd, - 0x647f11fb, - 0x647f51fc, - 0x647f91fd, - 0x48751fe, - 0x48d121d, - 0x494d234, - 0x49c5253, - 0x4a45271, - 0x4ab1291, - 0x4bdd2ac, - 0x4c352f7, - 0x64c3930d, - 0x4cd130e, - 0x4cd9334, - 0x24cdd336, - 0x4d65337, - 0x4db1359, - 0x4e1936c, - 0x4ec1386, - 0x4f893b0, - 0x4ff13e2, - 0x51053fc, - 0x65109441, - 0x6510d442, - 0x5169443, - 0x51c545a, - 0x5255471, - 0x52d1495, - 0x53154b4, - 0x53f94c5, - 0x542d4fe, - 0x548d50b, - 0x5501523, - 0x5589540, - 0x55c9562, - 0x5639572, - 0x6563d58e, - 0x566558f, - 0x5669599, - 0x568159a, - 0x569d5a0, - 0x56e15a7, - 0x56f15b8, - 0x57095bc, - 0x57815c2, - 0x57895e0, - 0x57a55e2, - 0x57b95e9, - 0x57d55ee, - 0x58015f5, - 0x5805600, - 0x580d601, - 0x5821603, - 0x5841608, - 0x5851610, - 0x585d614, - 0x5899617, - 0x58a1626, - 0x58b5628, - 0x58d962d, - 0x58e5636, - 0x58ed639, - 0x591163b, - 0x5935644, - 0x594d64d, - 0x5951653, - 0x5959654, - 0x595d656, - 0x59f9657, - 0x59fd67e, - 0x5a0167f, - 0x5a05680, - 0x5a29681, - 0x5a4d68a, - 0x5a69693, - 0x5a7d69a, - 0x5a9169f, - 0x5a996a4, - 0x5aa16a6, - 0x5aa96a8, - 0x5ac16aa, - 0x5ad16b0, - 0x5ad56b4, - 0x5af16b5, - 0x63796bc, - 0x63b18de, - 0x63dd8ec, - 0x63f98f7, - 0x64198fe, - 0x6439906, - 0x647d90e, - 0x648591f, - 0x26489921, - 0x2648d922, - 0x6495923, - 0x665d925, - 0x26661997, - 0x26671998, - 0x2667999c, - 0x2668599e, - 0x66899a1, - 0x266919a2, - 0x66999a4, - 0x66a99a6, - 0x66d19aa, - 0x67099b4, - 0x670d9c2, - 0x67459c3, - 0x67659d1, - 0x72bd9d9, - 0x72c1caf, - 0x72c5cb0, - 0x272c9cb1, - 0x72cdcb2, - 0x272d1cb3, - 0x72d5cb4, - 0x272e1cb5, - 0x72e5cb8, - 0x72e9cb9, - 0x272edcba, - 0x72f1cbb, - 0x272f9cbc, - 0x72fdcbe, - 0x7301cbf, - 0x27311cc0, - 0x7315cc4, - 0x7319cc5, - 0x731dcc6, - 0x7321cc7, - 0x27325cc8, - 0x7329cc9, - 0x732dcca, - 0x7331ccb, + 0x2da0b63, + 0x2da4b68, + 0x2da8b69, + 0x2dc8b6a, + 0x2de4b72, + 0x2de8b79, + 0x22decb7a, + 0x2df0b7b, + 0x2df4b7c, + 0x2df8b7d, + 0x2e00b7e, + 0x2e14b80, + 0x2e18b85, + 0x2e1cb86, + 0x2e44b87, + 0x2e48b91, + 0x2ebcb92, + 0x2ec0baf, + 0x2ec4bb0, + 0x2ee4bb1, + 0x2ef8bb9, + 0x2f0cbbe, + 0x2f24bc3, + 0x2f40bc9, + 0x2f58bd0, + 0x2f5cbd6, + 0x2f74bd7, + 0x2f90bdd, + 0x2f94be4, + 0x2fb4be5, + 0x2fd4bed, + 0x2ff0bf5, + 0x3054bfc, + 0x3070c15, + 0x3080c1c, + 0x3084c20, + 0x309cc21, + 0x30e0c27, + 0x3160c38, + 0x3190c58, + 0x3194c64, + 0x31a0c65, + 0x31c0c68, + 0x31c4c70, + 0x31e8c71, + 0x31f0c7a, + 0x322cc7c, + 0x327cc8b, + 0x3280c9f, + 0x3284ca0, + 0x3354ca1, + 0x23358cd5, + 0x2335ccd6, + 0x3360cd7, + 0x23364cd8, + 0x23368cd9, + 0x336ccda, + 0x23370cdb, + 0x23380cdc, + 0x23384ce0, + 0x23388ce1, + 0x2338cce2, + 0x23390ce3, + 0x33a8ce4, + 0x33cccea, + 0x33eccf3, + 0x3a58cfb, + 0x3a64e96, + 0x3a84e99, + 0x3c44ea1, + 0x3d14f11, + 0x3d84f45, + 0x3ddcf61, + 0x3ec4f77, + 0x3f1cfb1, + 0x3f58fc7, + 0x4054fd6, + 0x4121015, + 0x41b9048, + 0x424906e, + 0x42ad092, + 0x44e50ab, + 0x459d139, + 0x4669167, + 0x46b519a, + 0x473d1ad, + 0x47791cf, + 0x47c91de, + 0x48411f2, + 0x64845210, + 0x64849211, + 0x6484d212, + 0x48c9213, + 0x4925232, + 0x49a1249, + 0x4a19268, + 0x4a99286, + 0x4b052a6, + 0x4c312c1, + 0x4c8930c, + 0x64c8d322, + 0x4d25323, + 0x4d2d349, + 0x24d3134b, + 0x4db934c, + 0x4e0536e, + 0x4e6d381, + 0x4f1539b, + 0x4fdd3c5, + 0x50453f7, + 0x5159411, + 0x6515d456, + 0x65161457, + 0x51bd458, + 0x521946f, + 0x52a9486, + 0x53254aa, + 0x53694c9, + 0x544d4da, + 0x5481513, + 0x54e1520, + 0x5555538, + 0x55dd555, + 0x561d577, + 0x568d587, + 0x656915a3, + 0x56b95a4, + 0x56bd5ae, + 0x56d55af, + 0x56f15b5, + 0x57355bc, + 0x57455cd, + 0x575d5d1, + 0x57d55d7, + 0x57dd5f5, + 0x57f95f7, + 0x580d5fe, + 0x5829603, + 0x585560a, + 0x5859615, + 0x5861616, + 0x5875618, + 0x589561d, + 0x58a5625, + 0x58b1629, + 0x58ed62c, + 0x58f563b, + 0x590963d, + 0x592d642, + 0x593964b, + 0x594164e, + 0x5965650, + 0x5989659, + 0x59a1662, + 0x59a5668, + 0x59ad669, + 0x59b166b, + 0x5a5166c, + 0x5a55694, + 0x5a59695, + 0x5a5d696, + 0x5a81697, + 0x5aa56a0, + 0x5ac16a9, + 0x5ad56b0, + 0x5ae96b5, + 0x5af16ba, + 0x5af96bc, + 0x5b016be, + 0x5b196c0, + 0x5b296c6, + 0x5b2d6ca, + 0x5b496cb, + 0x63d16d2, + 0x64098f4, + 0x6435902, + 0x645190d, + 0x6471914, + 0x649191c, + 0x64d5924, + 0x64dd935, + 0x264e1937, + 0x264e5938, + 0x64ed939, + 0x66c593b, + 0x266c99b1, + 0x66cd9b2, + 0x266dd9b3, + 0x266e59b7, + 0x266f19b9, + 0x66f59bc, + 0x266fd9bd, + 0x67059bf, + 0x67159c1, + 0x673d9c5, + 0x67799cf, + 0x677d9de, + 0x67b59df, + 0x67d99ed, + 0x73319f6, 0x7335ccc, - 0x2733dccd, + 0x7339ccd, + 0x2733dcce, 0x7341ccf, - 0x7345cd0, + 0x27345cd0, 0x7349cd1, - 0x2734dcd2, - 0x7351cd3, - 0x27359cd4, - 0x2735dcd6, - 0x7379cd7, - 0x7391cde, - 0x27395ce4, - 0x73d9ce5, - 0x73ddcf6, - 0x7401cf7, - 0x740dd00, - 0x7411d03, - 0x7415d04, - 0x75d1d05, - 0x275d5d74, - 0x275ddd75, - 0x275e1d77, - 0x275e5d78, - 0x75edd79, - 0x76c9d7b, - 0x276d5db2, - 0x276d9db5, - 0x276dddb6, - 0x276e1db7, - 0x76e5db8, - 0x7711db9, - 0x7715dc4, - 0x7719dc5, - 0x773ddc6, - 0x7749dcf, - 0x7769dd2, - 0x776ddda, - 0x77a5ddb, - 0x7a55de9, - 0x7b11e95, - 0x7b15ec4, - 0x7b19ec5, - 0x7b2dec6, - 0x7b61ecb, - 0x7b99ed8, - 0x27b9dee6, - 0x7bb9ee7, - 0x7be1eee, - 0x7be5ef8, - 0x7c09ef9, - 0x7c25f02, - 0x7c4df09, - 0x7c5df13, - 0x7c61f17, - 0x7c65f18, - 0x7c9df19, - 0x7ca9f27, - 0x7cd1f2a, - 0x7d51f34, - 0x27d55f54, - 0x7d65f55, - 0x7d75f59, - 0x7d91f5d, - 0x7db1f64, - 0x7db5f6c, - 0x7dc9f6d, - 0x7dddf72, - 0x7de1f77, - 0x7de5f78, - 0x7e05f79, - 0x7eadf81, - 0x7eb1fab, - 0x7ecdfac, - 0x7ef1fb3, - 0x7ef5fbc, - 0x7efdfbd, - 0x7f19fbf, - 0x7f21fc6, - 0x7f35fc8, - 0x7f55fcd, - 0x7f71fd5, - 0x7f7dfdc, - 0x7f95fdf, - 0x7fcdfe5, - 0x80a1ff3, - 0x80a6028, - 0x80ba029, - 0x80c202e, - 0x80da030, - 0x80de036, - 0x80ea037, - 0x80ee03a, - 0x80f203b, - 0x811603c, - 0x8156045, - 0x815a055, - 0x817a056, - 0x81ca05e, - 0x81ea072, - 0x281ee07a, - 0x81f607b, - 0x824e07d, - 0x8252093, - 0x8256094, - 0x825a095, - 0x829e096, - 0x82ae0a7, - 0x82ee0ab, - 0x82f20bb, - 0x83220bc, - 0x846a0c8, - 0x849211a, - 0x84c2124, - 0x84e2130, - 0x284ea138, - 0x84f213a, - 0x84fe13c, - 0x861213f, - 0x861e184, - 0x862a187, - 0x863618a, - 0x864218d, - 0x864e190, - 0x865a193, - 0x8666196, - 0x8672199, - 0x867e19c, - 0x868a19f, - 0x86961a2, + 0x27355cd2, + 0x7359cd5, + 0x735dcd6, + 0x27361cd7, + 0x7365cd8, + 0x2736dcd9, + 0x7371cdb, + 0x7375cdc, + 0x27385cdd, + 0x7389ce1, + 0x738dce2, + 0x7391ce3, + 0x7395ce4, + 0x27399ce5, + 0x739dce6, + 0x73a1ce7, + 0x73a5ce8, + 0x73a9ce9, + 0x273b1cea, + 0x73b5cec, + 0x73b9ced, + 0x73bdcee, + 0x273c1cef, + 0x73c5cf0, + 0x273cdcf1, + 0x273d1cf3, + 0x73edcf4, + 0x7405cfb, + 0x27409d01, + 0x744dd02, + 0x7451d13, + 0x7475d14, + 0x7481d1d, + 0x7485d20, + 0x7489d21, + 0x7645d22, + 0x27649d91, + 0x27651d92, + 0x27655d94, + 0x27659d95, + 0x7661d96, + 0x773dd98, + 0x27749dcf, + 0x2774ddd2, + 0x27751dd3, + 0x27755dd4, + 0x7759dd5, + 0x7785dd6, + 0x7789de1, + 0x778dde2, + 0x77b1de3, + 0x77bddec, + 0x77dddef, + 0x77e1df7, + 0x7819df8, + 0x7ac9e06, + 0x7b85eb2, + 0x7b89ee1, + 0x7b8dee2, + 0x7ba1ee3, + 0x7bd5ee8, + 0x7c0def5, + 0x27c11f03, + 0x7c2df04, + 0x7c55f0b, + 0x7c59f15, + 0x7c7df16, + 0x7c99f1f, + 0x7cc1f26, + 0x7cd1f30, + 0x7cd5f34, + 0x7cd9f35, + 0x7d11f36, + 0x7d1df44, + 0x7d45f47, + 0x7dc5f51, + 0x27dc9f71, + 0x7dd9f72, + 0x7de9f76, + 0x7e05f7a, + 0x7e25f81, + 0x7e29f89, + 0x7e3df8a, + 0x7e51f8f, + 0x7e55f94, + 0x7e59f95, + 0x7e5df96, + 0x7e7df97, + 0x7f25f9f, + 0x7f29fc9, + 0x7f45fca, + 0x7f69fd1, + 0x7f6dfda, + 0x7f75fdb, + 0x7f91fdd, + 0x7f99fe4, + 0x7fadfe6, + 0x7fcdfeb, + 0x7fe9ff3, + 0x7ff5ffa, + 0x800dffd, + 0x8046003, + 0x811a011, + 0x811e046, + 0x8132047, + 0x813a04c, + 0x815204e, + 0x8156054, + 0x8162055, + 0x8166058, + 0x816a059, + 0x816e05a, + 0x819205b, + 0x81d2064, + 0x81d6074, + 0x81f6075, + 0x824607d, + 0x826a091, + 0x2826e09a, + 0x827609b, + 0x82ce09d, + 0x82d20b3, + 0x82d60b4, + 0x82da0b5, + 0x831e0b6, + 0x832e0c7, + 0x836e0cb, + 0x83720db, + 0x83a20dc, + 0x84ee0e8, + 0x851613b, + 0x8546145, + 0x8566151, + 0x2856e159, + 0x857615b, + 0x858215d, + 0x8696160, 0x86a21a5, 0x86ae1a8, - 0x86b61ab, - 0x86c21ad, - 0x86ce1b0, - 0x86da1b3, - 0x86e61b6, - 0x86f21b9, - 0x86fe1bc, - 0x870a1bf, - 0x87161c2, - 0x87221c5, - 0x872e1c8, - 0x873a1cb, - 0x87661ce, - 0x87721d9, - 0x877e1dc, - 0x878a1df, - 0x87961e2, - 0x87a21e5, - 0x87aa1e8, - 0x87b61ea, - 0x87c21ed, - 0x87ce1f0, - 0x87da1f3, - 0x87e61f6, - 0x87f21f9, - 0x87fe1fc, - 0x880a1ff, - 0x8816202, - 0x8822205, - 0x882e208, + 0x86ba1ab, + 0x86c61ae, + 0x86d21b1, + 0x86de1b4, + 0x86ea1b7, + 0x86f61ba, + 0x87021bd, + 0x870e1c0, + 0x871a1c3, + 0x87261c6, + 0x87321c9, + 0x873a1cc, + 0x87461ce, + 0x87521d1, + 0x875e1d4, + 0x876a1d7, + 0x87761da, + 0x87821dd, + 0x878e1e0, + 0x879a1e3, + 0x87a61e6, + 0x87b21e9, + 0x87be1ec, + 0x87ea1ef, + 0x87f61fa, + 0x88021fd, + 0x880e200, + 0x881a203, + 0x8826206, + 0x882e209, 0x883a20b, 0x884620e, - 0x884e211, - 0x885a213, - 0x8866216, - 0x8872219, - 0x887e21c, - 0x888a21f, - 0x8896222, - 0x88a2225, - 0x88ae228, - 0x88b222b, + 0x8852211, + 0x885e214, + 0x886a217, + 0x887621a, + 0x888221d, + 0x888e220, + 0x889a223, + 0x88a6226, + 0x88b2229, 0x88be22c, - 0x88da22f, - 0x88de236, - 0x88ee237, - 0x890e23b, - 0x8912243, - 0x8956244, - 0x895a255, - 0x896e256, - 0x89a225b, - 0x89b2268, - 0x89ba26c, - 0x89de26e, - 0x89f6277, - 0x8a0e27d, - 0x8a26283, - 0x8a3a289, - 0x28a8228e, - 0x8a862a0, - 0x8ab22a1, - 0x8ac22ac, - 0x8ad62b0, + 0x88ca22f, + 0x88d2232, + 0x88de234, + 0x88ea237, + 0x88f623a, + 0x890223d, + 0x890e240, + 0x891a243, + 0x8926246, + 0x8932249, + 0x893624c, + 0x894224d, + 0x895e250, + 0x8962257, + 0x8972258, + 0x899625c, + 0x899a265, + 0x89de266, + 0x89e2277, + 0x89f6278, + 0x8a2a27d, + 0x8a3a28a, + 0x8a4228e, + 0x8a66290, + 0x8a7e299, + 0x8a9629f, + 0x8aae2a5, + 0x8ac22ab, + 0x28b0a2b0, + 0x8b0e2c2, + 0x8b3a2c3, + 0x8b4a2ce, + 0x8b5e2d2, } -// max children 571 (capacity 1023) -// max text offset 30545 (capacity 32767) +// max children 580 (capacity 1023) +// max text offset 30618 (capacity 32767) // max text length 36 (capacity 63) -// max hi 8885 (capacity 16383) -// max lo 8880 (capacity 16383) +// max hi 8919 (capacity 16383) +// max lo 8914 (capacity 16383) diff --git a/vendor/golang.org/x/tools/cmd/goimports/goimports.go b/vendor/golang.org/x/tools/cmd/goimports/goimports.go index 2cca292359..c195caaf8b 100644 --- a/vendor/golang.org/x/tools/cmd/goimports/goimports.go +++ b/vendor/golang.org/x/tools/cmd/goimports/goimports.go @@ -160,7 +160,12 @@ func processFile(filename string, in io.Reader, out io.Writer, argType argumentT // filename is "" return errors.New("can't use -w on stdin") } - err = ioutil.WriteFile(filename, res, 0) + // On Windows, we need to re-set the permissions from the file. See golang/go#38225. + var perms os.FileMode + if fi, err := os.Stat(filename); err == nil { + perms = fi.Mode() & os.ModePerm + } + err = ioutil.WriteFile(filename, res, perms) if err != nil { return err } @@ -262,7 +267,7 @@ func gofmtMain() { if verbose { log.SetFlags(log.LstdFlags | log.Lmicroseconds) - options.Env.Debug = true + options.Env.Logf = log.Printf } if options.TabWidth < 0 { fmt.Fprintf(os.Stderr, "negative tabwidth %d\n", options.TabWidth) diff --git a/vendor/golang.org/x/tools/go/analysis/analysis.go b/vendor/golang.org/x/tools/go/analysis/analysis.go index ea605f4fd4..8c9977355c 100644 --- a/vendor/golang.org/x/tools/go/analysis/analysis.go +++ b/vendor/golang.org/x/tools/go/analysis/analysis.go @@ -7,6 +7,8 @@ import ( "go/token" "go/types" "reflect" + + "golang.org/x/tools/internal/analysisinternal" ) // An Analyzer describes an analysis function and its options. @@ -69,6 +71,17 @@ type Analyzer struct { func (a *Analyzer) String() string { return a.Name } +func init() { + // Set the analysisinternal functions to be able to pass type errors + // to the Pass type without modifying the go/analysis API. + analysisinternal.SetTypeErrors = func(p interface{}, errors []types.Error) { + p.(*Pass).typeErrors = errors + } + analysisinternal.GetTypeErrors = func(p interface{}) []types.Error { + return p.(*Pass).typeErrors + } +} + // A Pass provides information to the Run function that // applies a specific analyzer to a single Go package. // @@ -138,6 +151,9 @@ type Pass struct { // WARNING: This is an experimental API and may change in the future. AllObjectFacts func() []ObjectFact + // typeErrors contains types.Errors that are associated with the pkg. + typeErrors []types.Error + /* Further fields may be added in future. */ // For example, suggested or applied refactorings. } diff --git a/vendor/golang.org/x/tools/go/analysis/doc.go b/vendor/golang.org/x/tools/go/analysis/doc.go index 1b7b7ed5ae..ea56b724e8 100644 --- a/vendor/golang.org/x/tools/go/analysis/doc.go +++ b/vendor/golang.org/x/tools/go/analysis/doc.go @@ -1,6 +1,6 @@ /* -The analysis package defines the interface between a modular static +Package analysis defines the interface between a modular static analysis and an analysis driver program. @@ -70,39 +70,6 @@ A driver may use the name, flags, and documentation to provide on-line help that describes the analyses it performs. The doc comment contains a brief one-line summary, optionally followed by paragraphs of explanation. -The vet command, shown below, is an example of a driver that runs -multiple analyzers. It is based on the multichecker package -(see the "Standalone commands" section for details). - - $ go build golang.org/x/tools/go/analysis/cmd/vet - $ ./vet help - vet is a tool for static analysis of Go programs. - - Usage: vet [-flag] [package] - - Registered analyzers: - - asmdecl report mismatches between assembly files and Go declarations - assign check for useless assignments - atomic check for common mistakes using the sync/atomic package - ... - unusedresult check for unused results of calls to some functions - - $ ./vet help unusedresult - unusedresult: check for unused results of calls to some functions - - Analyzer flags: - - -unusedresult.funcs value - comma-separated list of functions whose results must be used (default Error,String) - -unusedresult.stringmethods value - comma-separated list of names of methods of type func() string whose results must be used - - Some functions like fmt.Errorf return a result and have no side effects, - so it is always a mistake to discard the result. This analyzer reports - calls to certain functions in which the result of the call is ignored. - - The set of functions may be controlled using flags. The Analyzer type has more fields besides those shown above: diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/vendor/golang.org/x/tools/go/ast/astutil/imports.go index 3e4b195368..2087ceec9c 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/imports.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/imports.go @@ -275,9 +275,10 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del // We deleted an entry but now there may be // a blank line-sized hole where the import was. - if line-lastLine > 1 { + if line-lastLine > 1 || !gen.Rparen.IsValid() { // There was a blank line immediately preceding the deleted import, - // so there's no need to close the hole. + // so there's no need to close the hole. The right parenthesis is + // invalid after AddImport to an import statement without parenthesis. // Do nothing. } else if line != fset.File(gen.Rparen).LineCount() { // There was no blank line. Close the hole. diff --git a/vendor/golang.org/x/tools/go/ast/inspector/inspector.go b/vendor/golang.org/x/tools/go/ast/inspector/inspector.go index ddbdd3f08f..af5e17feee 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/inspector.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/inspector.go @@ -90,7 +90,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { // The types argument, if non-empty, enables type-based filtering of // events. The function f if is called only for nodes whose type // matches an element of the types slice. -func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (prune bool)) { +func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) { mask := maskOf(types) for i := 0; i < len(in.events); { ev := in.events[i] @@ -114,7 +114,7 @@ func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (prun // supplies each call to f an additional argument, the current // traversal stack. The stack's first element is the outermost node, // an *ast.File; its last is the innermost, n. -func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (prune bool)) { +func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) { mask := maskOf(types) var stack []ast.Node for i := 0; i < len(in.events); { @@ -150,7 +150,11 @@ func traverse(files []*ast.File) []event { extent += int(f.End() - f.Pos()) } // This estimate is based on the net/http package. - events := make([]event, 0, extent*33/100) + capacity := extent * 33 / 100 + if capacity > 1e6 { + capacity = 1e6 // impose some reasonable maximum + } + events := make([]event, 0, capacity) var stack []event for _, f := range files { diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index f8363d8faa..0d51acad99 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -85,15 +85,11 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, return gcimporter.ImportData(imports, path, path, bytes.NewReader(data)) } - // The indexed export format starts with an 'i'; the older - // binary export format starts with a 'c', 'd', or 'v' - // (from "version"). Select appropriate importer. - if len(data) > 0 && data[0] == 'i' { - _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) - return pkg, err + // The indexed export format starts with an 'i'. + if len(data) == 0 || data[0] != 'i' { + return nil, fmt.Errorf("unknown export data format") } - - _, pkg, err := gcimporter.BImportData(fset, imports, data, path) + _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) return pkg, err } diff --git a/vendor/golang.org/x/tools/go/internal/cgo/cgo.go b/vendor/golang.org/x/tools/go/internal/cgo/cgo.go new file mode 100644 index 0000000000..5db8b30967 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/cgo/cgo.go @@ -0,0 +1,220 @@ +// Copyright 2013 The Go 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 cgo handles cgo preprocessing of files containing `import "C"`. +// +// DESIGN +// +// The approach taken is to run the cgo processor on the package's +// CgoFiles and parse the output, faking the filenames of the +// resulting ASTs so that the synthetic file containing the C types is +// called "C" (e.g. "~/go/src/net/C") and the preprocessed files +// have their original names (e.g. "~/go/src/net/cgo_unix.go"), +// not the names of the actual temporary files. +// +// The advantage of this approach is its fidelity to 'go build'. The +// downside is that the token.Position.Offset for each AST node is +// incorrect, being an offset within the temporary file. Line numbers +// should still be correct because of the //line comments. +// +// The logic of this file is mostly plundered from the 'go build' +// tool, which also invokes the cgo preprocessor. +// +// +// REJECTED ALTERNATIVE +// +// An alternative approach that we explored is to extend go/types' +// Importer mechanism to provide the identity of the importing package +// so that each time `import "C"` appears it resolves to a different +// synthetic package containing just the objects needed in that case. +// The loader would invoke cgo but parse only the cgo_types.go file +// defining the package-level objects, discarding the other files +// resulting from preprocessing. +// +// The benefit of this approach would have been that source-level +// syntax information would correspond exactly to the original cgo +// file, with no preprocessing involved, making source tools like +// godoc, guru, and eg happy. However, the approach was rejected +// due to the additional complexity it would impose on go/types. (It +// made for a beautiful demo, though.) +// +// cgo files, despite their *.go extension, are not legal Go source +// files per the specification since they may refer to unexported +// members of package "C" such as C.int. Also, a function such as +// C.getpwent has in effect two types, one matching its C type and one +// which additionally returns (errno C.int). The cgo preprocessor +// uses name mangling to distinguish these two functions in the +// processed code, but go/types would need to duplicate this logic in +// its handling of function calls, analogous to the treatment of map +// lookups in which y=m[k] and y,ok=m[k] are both legal. + +package cgo + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// ProcessFiles invokes the cgo preprocessor on bp.CgoFiles, parses +// the output and returns the resulting ASTs. +// +func ProcessFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) { + tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + + pkgdir := bp.Dir + if DisplayPath != nil { + pkgdir = DisplayPath(pkgdir) + } + + cgoFiles, cgoDisplayFiles, err := Run(bp, pkgdir, tmpdir, false) + if err != nil { + return nil, err + } + var files []*ast.File + for i := range cgoFiles { + rd, err := os.Open(cgoFiles[i]) + if err != nil { + return nil, err + } + display := filepath.Join(bp.Dir, cgoDisplayFiles[i]) + f, err := parser.ParseFile(fset, display, rd, mode) + rd.Close() + if err != nil { + return nil, err + } + files = append(files, f) + } + return files, nil +} + +var cgoRe = regexp.MustCompile(`[/\\:]`) + +// Run invokes the cgo preprocessor on bp.CgoFiles and returns two +// lists of files: the resulting processed files (in temporary +// directory tmpdir) and the corresponding names of the unprocessed files. +// +// Run is adapted from (*builder).cgo in +// $GOROOT/src/cmd/go/build.go, but these features are unsupported: +// Objective C, CGOPKGPATH, CGO_FLAGS. +// +// If useabs is set to true, absolute paths of the bp.CgoFiles will be passed in +// to the cgo preprocessor. This in turn will set the // line comments +// referring to those files to use absolute paths. This is needed for +// go/packages using the legacy go list support so it is able to find +// the original files. +func Run(bp *build.Package, pkgdir, tmpdir string, useabs bool) (files, displayFiles []string, err error) { + cgoCPPFLAGS, _, _, _ := cflags(bp, true) + _, cgoexeCFLAGS, _, _ := cflags(bp, false) + + if len(bp.CgoPkgConfig) > 0 { + pcCFLAGS, err := pkgConfigFlags(bp) + if err != nil { + return nil, nil, err + } + cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...) + } + + // Allows including _cgo_export.h from .[ch] files in the package. + cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir) + + // _cgo_gotypes.go (displayed "C") contains the type definitions. + files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go")) + displayFiles = append(displayFiles, "C") + for _, fn := range bp.CgoFiles { + // "foo.cgo1.go" (displayed "foo.go") is the processed Go source. + f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_") + files = append(files, filepath.Join(tmpdir, f+"cgo1.go")) + displayFiles = append(displayFiles, fn) + } + + var cgoflags []string + if bp.Goroot && bp.ImportPath == "runtime/cgo" { + cgoflags = append(cgoflags, "-import_runtime_cgo=false") + } + if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" { + cgoflags = append(cgoflags, "-import_syscall=false") + } + + var cgoFiles []string = bp.CgoFiles + if useabs { + cgoFiles = make([]string, len(bp.CgoFiles)) + for i := range cgoFiles { + cgoFiles[i] = filepath.Join(pkgdir, bp.CgoFiles[i]) + } + } + + args := stringList( + "go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--", + cgoCPPFLAGS, cgoexeCFLAGS, cgoFiles, + ) + if false { + log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir) + } + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = pkgdir + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err) + } + + return files, displayFiles, nil +} + +// -- unmodified from 'go build' --------------------------------------- + +// Return the flags to use when invoking the C or C++ compilers, or cgo. +func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { + var defaults string + if def { + defaults = "-g -O2" + } + + cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) + cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) + cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) + ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) + return +} + +// envList returns the value of the given environment variable broken +// into fields, using the default value when the variable is empty. +func envList(key, def string) []string { + v := os.Getenv(key) + if v == "" { + v = def + } + return strings.Fields(v) +} + +// stringList's arguments should be a sequence of string or []string values. +// stringList flattens them into a single []string. +func stringList(args ...interface{}) []string { + var x []string + for _, arg := range args { + switch arg := arg.(type) { + case []string: + x = append(x, arg...) + case string: + x = append(x, arg) + default: + panic("stringList: invalid argument") + } + } + return x +} diff --git a/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go b/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go new file mode 100644 index 0000000000..b5bb95a63e --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/cgo/cgo_pkgconfig.go @@ -0,0 +1,39 @@ +// Copyright 2013 The Go 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 cgo + +import ( + "errors" + "fmt" + "go/build" + "os/exec" + "strings" +) + +// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints. +func pkgConfig(mode string, pkgs []string) (flags []string, err error) { + cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...) + out, err := cmd.CombinedOutput() + if err != nil { + s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err) + if len(out) > 0 { + s = fmt.Sprintf("%s: %s", s, out) + } + return nil, errors.New(s) + } + if len(out) > 0 { + flags = strings.Fields(string(out)) + } + return +} + +// pkgConfigFlags calls pkg-config if needed and returns the cflags +// needed to build the package. +func pkgConfigFlags(p *build.Package) (cflags []string, err error) { + if len(p.CgoPkgConfig) == 0 { + return nil, nil + } + return pkgConfig("--cflags", p.CgoPkgConfig) +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go deleted file mode 100644 index a807d0aaa2..0000000000 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/bexport.go +++ /dev/null @@ -1,852 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Binary package export. -// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go; -// see that file for specification of the format. - -package gcimporter - -import ( - "bytes" - "encoding/binary" - "fmt" - "go/ast" - "go/constant" - "go/token" - "go/types" - "math" - "math/big" - "sort" - "strings" -) - -// If debugFormat is set, each integer and string value is preceded by a marker -// and position information in the encoding. This mechanism permits an importer -// to recognize immediately when it is out of sync. The importer recognizes this -// mode automatically (i.e., it can import export data produced with debugging -// support even if debugFormat is not set at the time of import). This mode will -// lead to massively larger export data (by a factor of 2 to 3) and should only -// be enabled during development and debugging. -// -// NOTE: This flag is the first flag to enable if importing dies because of -// (suspected) format errors, and whenever a change is made to the format. -const debugFormat = false // default: false - -// If trace is set, debugging output is printed to std out. -const trace = false // default: false - -// Current export format version. Increase with each format change. -// Note: The latest binary (non-indexed) export format is at version 6. -// This exporter is still at level 4, but it doesn't matter since -// the binary importer can handle older versions just fine. -// 6: package height (CL 105038) -- NOT IMPLEMENTED HERE -// 5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMEMTED HERE -// 4: type name objects support type aliases, uses aliasTag -// 3: Go1.8 encoding (same as version 2, aliasTag defined but never used) -// 2: removed unused bool in ODCL export (compiler only) -// 1: header format change (more regular), export package for _ struct fields -// 0: Go1.7 encoding -const exportVersion = 4 - -// trackAllTypes enables cycle tracking for all types, not just named -// types. The existing compiler invariants assume that unnamed types -// that are not completely set up are not used, or else there are spurious -// errors. -// If disabled, only named types are tracked, possibly leading to slightly -// less efficient encoding in rare cases. It also prevents the export of -// some corner-case type declarations (but those are not handled correctly -// with with the textual export format either). -// TODO(gri) enable and remove once issues caused by it are fixed -const trackAllTypes = false - -type exporter struct { - fset *token.FileSet - out bytes.Buffer - - // object -> index maps, indexed in order of serialization - strIndex map[string]int - pkgIndex map[*types.Package]int - typIndex map[types.Type]int - - // position encoding - posInfoFormat bool - prevFile string - prevLine int - - // debugging support - written int // bytes written - indent int // for trace -} - -// internalError represents an error generated inside this package. -type internalError string - -func (e internalError) Error() string { return "gcimporter: " + string(e) } - -func internalErrorf(format string, args ...interface{}) error { - return internalError(fmt.Sprintf(format, args...)) -} - -// BExportData returns binary export data for pkg. -// If no file set is provided, position info will be missing. -func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { - defer func() { - if e := recover(); e != nil { - if ierr, ok := e.(internalError); ok { - err = ierr - return - } - // Not an internal error; panic again. - panic(e) - } - }() - - p := exporter{ - fset: fset, - strIndex: map[string]int{"": 0}, // empty string is mapped to 0 - pkgIndex: make(map[*types.Package]int), - typIndex: make(map[types.Type]int), - posInfoFormat: true, // TODO(gri) might become a flag, eventually - } - - // write version info - // The version string must start with "version %d" where %d is the version - // number. Additional debugging information may follow after a blank; that - // text is ignored by the importer. - p.rawStringln(fmt.Sprintf("version %d", exportVersion)) - var debug string - if debugFormat { - debug = "debug" - } - p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly - p.bool(trackAllTypes) - p.bool(p.posInfoFormat) - - // --- generic export data --- - - // populate type map with predeclared "known" types - for index, typ := range predeclared() { - p.typIndex[typ] = index - } - if len(p.typIndex) != len(predeclared()) { - return nil, internalError("duplicate entries in type map?") - } - - // write package data - p.pkg(pkg, true) - if trace { - p.tracef("\n") - } - - // write objects - objcount := 0 - scope := pkg.Scope() - for _, name := range scope.Names() { - if !ast.IsExported(name) { - continue - } - if trace { - p.tracef("\n") - } - p.obj(scope.Lookup(name)) - objcount++ - } - - // indicate end of list - if trace { - p.tracef("\n") - } - p.tag(endTag) - - // for self-verification only (redundant) - p.int(objcount) - - if trace { - p.tracef("\n") - } - - // --- end of export data --- - - return p.out.Bytes(), nil -} - -func (p *exporter) pkg(pkg *types.Package, emptypath bool) { - if pkg == nil { - panic(internalError("unexpected nil pkg")) - } - - // if we saw the package before, write its index (>= 0) - if i, ok := p.pkgIndex[pkg]; ok { - p.index('P', i) - return - } - - // otherwise, remember the package, write the package tag (< 0) and package data - if trace { - p.tracef("P%d = { ", len(p.pkgIndex)) - defer p.tracef("} ") - } - p.pkgIndex[pkg] = len(p.pkgIndex) - - p.tag(packageTag) - p.string(pkg.Name()) - if emptypath { - p.string("") - } else { - p.string(pkg.Path()) - } -} - -func (p *exporter) obj(obj types.Object) { - switch obj := obj.(type) { - case *types.Const: - p.tag(constTag) - p.pos(obj) - p.qualifiedName(obj) - p.typ(obj.Type()) - p.value(obj.Val()) - - case *types.TypeName: - if obj.IsAlias() { - p.tag(aliasTag) - p.pos(obj) - p.qualifiedName(obj) - } else { - p.tag(typeTag) - } - p.typ(obj.Type()) - - case *types.Var: - p.tag(varTag) - p.pos(obj) - p.qualifiedName(obj) - p.typ(obj.Type()) - - case *types.Func: - p.tag(funcTag) - p.pos(obj) - p.qualifiedName(obj) - sig := obj.Type().(*types.Signature) - p.paramList(sig.Params(), sig.Variadic()) - p.paramList(sig.Results(), false) - - default: - panic(internalErrorf("unexpected object %v (%T)", obj, obj)) - } -} - -func (p *exporter) pos(obj types.Object) { - if !p.posInfoFormat { - return - } - - file, line := p.fileLine(obj) - if file == p.prevFile { - // common case: write line delta - // delta == 0 means different file or no line change - delta := line - p.prevLine - p.int(delta) - if delta == 0 { - p.int(-1) // -1 means no file change - } - } else { - // different file - p.int(0) - // Encode filename as length of common prefix with previous - // filename, followed by (possibly empty) suffix. Filenames - // frequently share path prefixes, so this can save a lot - // of space and make export data size less dependent on file - // path length. The suffix is unlikely to be empty because - // file names tend to end in ".go". - n := commonPrefixLen(p.prevFile, file) - p.int(n) // n >= 0 - p.string(file[n:]) // write suffix only - p.prevFile = file - p.int(line) - } - p.prevLine = line -} - -func (p *exporter) fileLine(obj types.Object) (file string, line int) { - if p.fset != nil { - pos := p.fset.Position(obj.Pos()) - file = pos.Filename - line = pos.Line - } - return -} - -func commonPrefixLen(a, b string) int { - if len(a) > len(b) { - a, b = b, a - } - // len(a) <= len(b) - i := 0 - for i < len(a) && a[i] == b[i] { - i++ - } - return i -} - -func (p *exporter) qualifiedName(obj types.Object) { - p.string(obj.Name()) - p.pkg(obj.Pkg(), false) -} - -func (p *exporter) typ(t types.Type) { - if t == nil { - panic(internalError("nil type")) - } - - // Possible optimization: Anonymous pointer types *T where - // T is a named type are common. We could canonicalize all - // such types *T to a single type PT = *T. This would lead - // to at most one *T entry in typIndex, and all future *T's - // would be encoded as the respective index directly. Would - // save 1 byte (pointerTag) per *T and reduce the typIndex - // size (at the cost of a canonicalization map). We can do - // this later, without encoding format change. - - // if we saw the type before, write its index (>= 0) - if i, ok := p.typIndex[t]; ok { - p.index('T', i) - return - } - - // otherwise, remember the type, write the type tag (< 0) and type data - if trackAllTypes { - if trace { - p.tracef("T%d = {>\n", len(p.typIndex)) - defer p.tracef("<\n} ") - } - p.typIndex[t] = len(p.typIndex) - } - - switch t := t.(type) { - case *types.Named: - if !trackAllTypes { - // if we don't track all types, track named types now - p.typIndex[t] = len(p.typIndex) - } - - p.tag(namedTag) - p.pos(t.Obj()) - p.qualifiedName(t.Obj()) - p.typ(t.Underlying()) - if !types.IsInterface(t) { - p.assocMethods(t) - } - - case *types.Array: - p.tag(arrayTag) - p.int64(t.Len()) - p.typ(t.Elem()) - - case *types.Slice: - p.tag(sliceTag) - p.typ(t.Elem()) - - case *dddSlice: - p.tag(dddTag) - p.typ(t.elem) - - case *types.Struct: - p.tag(structTag) - p.fieldList(t) - - case *types.Pointer: - p.tag(pointerTag) - p.typ(t.Elem()) - - case *types.Signature: - p.tag(signatureTag) - p.paramList(t.Params(), t.Variadic()) - p.paramList(t.Results(), false) - - case *types.Interface: - p.tag(interfaceTag) - p.iface(t) - - case *types.Map: - p.tag(mapTag) - p.typ(t.Key()) - p.typ(t.Elem()) - - case *types.Chan: - p.tag(chanTag) - p.int(int(3 - t.Dir())) // hack - p.typ(t.Elem()) - - default: - panic(internalErrorf("unexpected type %T: %s", t, t)) - } -} - -func (p *exporter) assocMethods(named *types.Named) { - // Sort methods (for determinism). - var methods []*types.Func - for i := 0; i < named.NumMethods(); i++ { - methods = append(methods, named.Method(i)) - } - sort.Sort(methodsByName(methods)) - - p.int(len(methods)) - - if trace && methods != nil { - p.tracef("associated methods {>\n") - } - - for i, m := range methods { - if trace && i > 0 { - p.tracef("\n") - } - - p.pos(m) - name := m.Name() - p.string(name) - if !exported(name) { - p.pkg(m.Pkg(), false) - } - - sig := m.Type().(*types.Signature) - p.paramList(types.NewTuple(sig.Recv()), false) - p.paramList(sig.Params(), sig.Variadic()) - p.paramList(sig.Results(), false) - p.int(0) // dummy value for go:nointerface pragma - ignored by importer - } - - if trace && methods != nil { - p.tracef("<\n} ") - } -} - -type methodsByName []*types.Func - -func (x methodsByName) Len() int { return len(x) } -func (x methodsByName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() } - -func (p *exporter) fieldList(t *types.Struct) { - if trace && t.NumFields() > 0 { - p.tracef("fields {>\n") - defer p.tracef("<\n} ") - } - - p.int(t.NumFields()) - for i := 0; i < t.NumFields(); i++ { - if trace && i > 0 { - p.tracef("\n") - } - p.field(t.Field(i)) - p.string(t.Tag(i)) - } -} - -func (p *exporter) field(f *types.Var) { - if !f.IsField() { - panic(internalError("field expected")) - } - - p.pos(f) - p.fieldName(f) - p.typ(f.Type()) -} - -func (p *exporter) iface(t *types.Interface) { - // TODO(gri): enable importer to load embedded interfaces, - // then emit Embeddeds and ExplicitMethods separately here. - p.int(0) - - n := t.NumMethods() - if trace && n > 0 { - p.tracef("methods {>\n") - defer p.tracef("<\n} ") - } - p.int(n) - for i := 0; i < n; i++ { - if trace && i > 0 { - p.tracef("\n") - } - p.method(t.Method(i)) - } -} - -func (p *exporter) method(m *types.Func) { - sig := m.Type().(*types.Signature) - if sig.Recv() == nil { - panic(internalError("method expected")) - } - - p.pos(m) - p.string(m.Name()) - if m.Name() != "_" && !ast.IsExported(m.Name()) { - p.pkg(m.Pkg(), false) - } - - // interface method; no need to encode receiver. - p.paramList(sig.Params(), sig.Variadic()) - p.paramList(sig.Results(), false) -} - -func (p *exporter) fieldName(f *types.Var) { - name := f.Name() - - if f.Anonymous() { - // anonymous field - we distinguish between 3 cases: - // 1) field name matches base type name and is exported - // 2) field name matches base type name and is not exported - // 3) field name doesn't match base type name (alias name) - bname := basetypeName(f.Type()) - if name == bname { - if ast.IsExported(name) { - name = "" // 1) we don't need to know the field name or package - } else { - name = "?" // 2) use unexported name "?" to force package export - } - } else { - // 3) indicate alias and export name as is - // (this requires an extra "@" but this is a rare case) - p.string("@") - } - } - - p.string(name) - if name != "" && !ast.IsExported(name) { - p.pkg(f.Pkg(), false) - } -} - -func basetypeName(typ types.Type) string { - switch typ := deref(typ).(type) { - case *types.Basic: - return typ.Name() - case *types.Named: - return typ.Obj().Name() - default: - return "" // unnamed type - } -} - -func (p *exporter) paramList(params *types.Tuple, variadic bool) { - // use negative length to indicate unnamed parameters - // (look at the first parameter only since either all - // names are present or all are absent) - n := params.Len() - if n > 0 && params.At(0).Name() == "" { - n = -n - } - p.int(n) - for i := 0; i < params.Len(); i++ { - q := params.At(i) - t := q.Type() - if variadic && i == params.Len()-1 { - t = &dddSlice{t.(*types.Slice).Elem()} - } - p.typ(t) - if n > 0 { - name := q.Name() - p.string(name) - if name != "_" { - p.pkg(q.Pkg(), false) - } - } - p.string("") // no compiler-specific info - } -} - -func (p *exporter) value(x constant.Value) { - if trace { - p.tracef("= ") - } - - switch x.Kind() { - case constant.Bool: - tag := falseTag - if constant.BoolVal(x) { - tag = trueTag - } - p.tag(tag) - - case constant.Int: - if v, exact := constant.Int64Val(x); exact { - // common case: x fits into an int64 - use compact encoding - p.tag(int64Tag) - p.int64(v) - return - } - // uncommon case: large x - use float encoding - // (powers of 2 will be encoded efficiently with exponent) - p.tag(floatTag) - p.float(constant.ToFloat(x)) - - case constant.Float: - p.tag(floatTag) - p.float(x) - - case constant.Complex: - p.tag(complexTag) - p.float(constant.Real(x)) - p.float(constant.Imag(x)) - - case constant.String: - p.tag(stringTag) - p.string(constant.StringVal(x)) - - case constant.Unknown: - // package contains type errors - p.tag(unknownTag) - - default: - panic(internalErrorf("unexpected value %v (%T)", x, x)) - } -} - -func (p *exporter) float(x constant.Value) { - if x.Kind() != constant.Float { - panic(internalErrorf("unexpected constant %v, want float", x)) - } - // extract sign (there is no -0) - sign := constant.Sign(x) - if sign == 0 { - // x == 0 - p.int(0) - return - } - // x != 0 - - var f big.Float - if v, exact := constant.Float64Val(x); exact { - // float64 - f.SetFloat64(v) - } else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int { - // TODO(gri): add big.Rat accessor to constant.Value. - r := valueToRat(num) - f.SetRat(r.Quo(r, valueToRat(denom))) - } else { - // Value too large to represent as a fraction => inaccessible. - // TODO(gri): add big.Float accessor to constant.Value. - f.SetFloat64(math.MaxFloat64) // FIXME - } - - // extract exponent such that 0.5 <= m < 1.0 - var m big.Float - exp := f.MantExp(&m) - - // extract mantissa as *big.Int - // - set exponent large enough so mant satisfies mant.IsInt() - // - get *big.Int from mant - m.SetMantExp(&m, int(m.MinPrec())) - mant, acc := m.Int(nil) - if acc != big.Exact { - panic(internalError("internal error")) - } - - p.int(sign) - p.int(exp) - p.string(string(mant.Bytes())) -} - -func valueToRat(x constant.Value) *big.Rat { - // Convert little-endian to big-endian. - // I can't believe this is necessary. - bytes := constant.Bytes(x) - for i := 0; i < len(bytes)/2; i++ { - bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] - } - return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) -} - -func (p *exporter) bool(b bool) bool { - if trace { - p.tracef("[") - defer p.tracef("= %v] ", b) - } - - x := 0 - if b { - x = 1 - } - p.int(x) - return b -} - -// ---------------------------------------------------------------------------- -// Low-level encoders - -func (p *exporter) index(marker byte, index int) { - if index < 0 { - panic(internalError("invalid index < 0")) - } - if debugFormat { - p.marker('t') - } - if trace { - p.tracef("%c%d ", marker, index) - } - p.rawInt64(int64(index)) -} - -func (p *exporter) tag(tag int) { - if tag >= 0 { - panic(internalError("invalid tag >= 0")) - } - if debugFormat { - p.marker('t') - } - if trace { - p.tracef("%s ", tagString[-tag]) - } - p.rawInt64(int64(tag)) -} - -func (p *exporter) int(x int) { - p.int64(int64(x)) -} - -func (p *exporter) int64(x int64) { - if debugFormat { - p.marker('i') - } - if trace { - p.tracef("%d ", x) - } - p.rawInt64(x) -} - -func (p *exporter) string(s string) { - if debugFormat { - p.marker('s') - } - if trace { - p.tracef("%q ", s) - } - // if we saw the string before, write its index (>= 0) - // (the empty string is mapped to 0) - if i, ok := p.strIndex[s]; ok { - p.rawInt64(int64(i)) - return - } - // otherwise, remember string and write its negative length and bytes - p.strIndex[s] = len(p.strIndex) - p.rawInt64(-int64(len(s))) - for i := 0; i < len(s); i++ { - p.rawByte(s[i]) - } -} - -// marker emits a marker byte and position information which makes -// it easy for a reader to detect if it is "out of sync". Used for -// debugFormat format only. -func (p *exporter) marker(m byte) { - p.rawByte(m) - // Enable this for help tracking down the location - // of an incorrect marker when running in debugFormat. - if false && trace { - p.tracef("#%d ", p.written) - } - p.rawInt64(int64(p.written)) -} - -// rawInt64 should only be used by low-level encoders. -func (p *exporter) rawInt64(x int64) { - var tmp [binary.MaxVarintLen64]byte - n := binary.PutVarint(tmp[:], x) - for i := 0; i < n; i++ { - p.rawByte(tmp[i]) - } -} - -// rawStringln should only be used to emit the initial version string. -func (p *exporter) rawStringln(s string) { - for i := 0; i < len(s); i++ { - p.rawByte(s[i]) - } - p.rawByte('\n') -} - -// rawByte is the bottleneck interface to write to p.out. -// rawByte escapes b as follows (any encoding does that -// hides '$'): -// -// '$' => '|' 'S' -// '|' => '|' '|' -// -// Necessary so other tools can find the end of the -// export data by searching for "$$". -// rawByte should only be used by low-level encoders. -func (p *exporter) rawByte(b byte) { - switch b { - case '$': - // write '$' as '|' 'S' - b = 'S' - fallthrough - case '|': - // write '|' as '|' '|' - p.out.WriteByte('|') - p.written++ - } - p.out.WriteByte(b) - p.written++ -} - -// tracef is like fmt.Printf but it rewrites the format string -// to take care of indentation. -func (p *exporter) tracef(format string, args ...interface{}) { - if strings.ContainsAny(format, "<>\n") { - var buf bytes.Buffer - for i := 0; i < len(format); i++ { - // no need to deal with runes - ch := format[i] - switch ch { - case '>': - p.indent++ - continue - case '<': - p.indent-- - continue - } - buf.WriteByte(ch) - if ch == '\n' { - for j := p.indent; j > 0; j-- { - buf.WriteString(". ") - } - } - } - format = buf.String() - } - fmt.Printf(format, args...) -} - -// Debugging support. -// (tagString is only used when tracing is enabled) -var tagString = [...]string{ - // Packages - -packageTag: "package", - - // Types - -namedTag: "named type", - -arrayTag: "array", - -sliceTag: "slice", - -dddTag: "ddd", - -structTag: "struct", - -pointerTag: "pointer", - -signatureTag: "signature", - -interfaceTag: "interface", - -mapTag: "map", - -chanTag: "chan", - - // Values - -falseTag: "false", - -trueTag: "true", - -int64Tag: "int64", - -floatTag: "float", - -fractionTag: "fraction", - -complexTag: "complex", - -stringTag: "string", - -unknownTag: "unknown", - - // Type aliases - -aliasTag: "alias", -} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go deleted file mode 100644 index e9f73d14a1..0000000000 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go +++ /dev/null @@ -1,1039 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file is a copy of $GOROOT/src/go/internal/gcimporter/bimport.go. - -package gcimporter - -import ( - "encoding/binary" - "fmt" - "go/constant" - "go/token" - "go/types" - "sort" - "strconv" - "strings" - "sync" - "unicode" - "unicode/utf8" -) - -type importer struct { - imports map[string]*types.Package - data []byte - importpath string - buf []byte // for reading strings - version int // export format version - - // object lists - strList []string // in order of appearance - pathList []string // in order of appearance - pkgList []*types.Package // in order of appearance - typList []types.Type // in order of appearance - interfaceList []*types.Interface // for delayed completion only - trackAllTypes bool - - // position encoding - posInfoFormat bool - prevFile string - prevLine int - fake fakeFileSet - - // debugging support - debugFormat bool - read int // bytes read -} - -// BImportData imports a package from the serialized package data -// and returns the number of bytes consumed and a reference to the package. -// If the export data version is not recognized or the format is otherwise -// compromised, an error is returned. -func BImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { - // catch panics and return them as errors - const currentVersion = 6 - version := -1 // unknown version - defer func() { - if e := recover(); e != nil { - // Return a (possibly nil or incomplete) package unchanged (see #16088). - if version > currentVersion { - err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) - } else { - err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) - } - } - }() - - p := importer{ - imports: imports, - data: data, - importpath: path, - version: version, - strList: []string{""}, // empty string is mapped to 0 - pathList: []string{""}, // empty string is mapped to 0 - fake: fakeFileSet{ - fset: fset, - files: make(map[string]*token.File), - }, - } - - // read version info - var versionstr string - if b := p.rawByte(); b == 'c' || b == 'd' { - // Go1.7 encoding; first byte encodes low-level - // encoding format (compact vs debug). - // For backward-compatibility only (avoid problems with - // old installed packages). Newly compiled packages use - // the extensible format string. - // TODO(gri) Remove this support eventually; after Go1.8. - if b == 'd' { - p.debugFormat = true - } - p.trackAllTypes = p.rawByte() == 'a' - p.posInfoFormat = p.int() != 0 - versionstr = p.string() - if versionstr == "v1" { - version = 0 - } - } else { - // Go1.8 extensible encoding - // read version string and extract version number (ignore anything after the version number) - versionstr = p.rawStringln(b) - if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" { - if v, err := strconv.Atoi(s[1]); err == nil && v > 0 { - version = v - } - } - } - p.version = version - - // read version specific flags - extend as necessary - switch p.version { - // case currentVersion: - // ... - // fallthrough - case currentVersion, 5, 4, 3, 2, 1: - p.debugFormat = p.rawStringln(p.rawByte()) == "debug" - p.trackAllTypes = p.int() != 0 - p.posInfoFormat = p.int() != 0 - case 0: - // Go1.7 encoding format - nothing to do here - default: - errorf("unknown bexport format version %d (%q)", p.version, versionstr) - } - - // --- generic export data --- - - // populate typList with predeclared "known" types - p.typList = append(p.typList, predeclared()...) - - // read package data - pkg = p.pkg() - - // read objects of phase 1 only (see cmd/compile/internal/gc/bexport.go) - objcount := 0 - for { - tag := p.tagOrIndex() - if tag == endTag { - break - } - p.obj(tag) - objcount++ - } - - // self-verification - if count := p.int(); count != objcount { - errorf("got %d objects; want %d", objcount, count) - } - - // ignore compiler-specific import data - - // complete interfaces - // TODO(gri) re-investigate if we still need to do this in a delayed fashion - for _, typ := range p.interfaceList { - typ.Complete() - } - - // record all referenced packages as imports - list := append(([]*types.Package)(nil), p.pkgList[1:]...) - sort.Sort(byPath(list)) - pkg.SetImports(list) - - // package was imported completely and without errors - pkg.MarkComplete() - - return p.read, pkg, nil -} - -func errorf(format string, args ...interface{}) { - panic(fmt.Sprintf(format, args...)) -} - -func (p *importer) pkg() *types.Package { - // if the package was seen before, i is its index (>= 0) - i := p.tagOrIndex() - if i >= 0 { - return p.pkgList[i] - } - - // otherwise, i is the package tag (< 0) - if i != packageTag { - errorf("unexpected package tag %d version %d", i, p.version) - } - - // read package data - name := p.string() - var path string - if p.version >= 5 { - path = p.path() - } else { - path = p.string() - } - if p.version >= 6 { - p.int() // package height; unused by go/types - } - - // we should never see an empty package name - if name == "" { - errorf("empty package name in import") - } - - // an empty path denotes the package we are currently importing; - // it must be the first package we see - if (path == "") != (len(p.pkgList) == 0) { - errorf("package path %q for pkg index %d", path, len(p.pkgList)) - } - - // if the package was imported before, use that one; otherwise create a new one - if path == "" { - path = p.importpath - } - pkg := p.imports[path] - if pkg == nil { - pkg = types.NewPackage(path, name) - p.imports[path] = pkg - } else if pkg.Name() != name { - errorf("conflicting names %s and %s for package %q", pkg.Name(), name, path) - } - p.pkgList = append(p.pkgList, pkg) - - return pkg -} - -// objTag returns the tag value for each object kind. -func objTag(obj types.Object) int { - switch obj.(type) { - case *types.Const: - return constTag - case *types.TypeName: - return typeTag - case *types.Var: - return varTag - case *types.Func: - return funcTag - default: - errorf("unexpected object: %v (%T)", obj, obj) // panics - panic("unreachable") - } -} - -func sameObj(a, b types.Object) bool { - // Because unnamed types are not canonicalized, we cannot simply compare types for - // (pointer) identity. - // Ideally we'd check equality of constant values as well, but this is good enough. - return objTag(a) == objTag(b) && types.Identical(a.Type(), b.Type()) -} - -func (p *importer) declare(obj types.Object) { - pkg := obj.Pkg() - if alt := pkg.Scope().Insert(obj); alt != nil { - // This can only trigger if we import a (non-type) object a second time. - // Excluding type aliases, this cannot happen because 1) we only import a package - // once; and b) we ignore compiler-specific export data which may contain - // functions whose inlined function bodies refer to other functions that - // were already imported. - // However, type aliases require reexporting the original type, so we need - // to allow it (see also the comment in cmd/compile/internal/gc/bimport.go, - // method importer.obj, switch case importing functions). - // TODO(gri) review/update this comment once the gc compiler handles type aliases. - if !sameObj(obj, alt) { - errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", obj, alt) - } - } -} - -func (p *importer) obj(tag int) { - switch tag { - case constTag: - pos := p.pos() - pkg, name := p.qualifiedName() - typ := p.typ(nil, nil) - val := p.value() - p.declare(types.NewConst(pos, pkg, name, typ, val)) - - case aliasTag: - // TODO(gri) verify type alias hookup is correct - pos := p.pos() - pkg, name := p.qualifiedName() - typ := p.typ(nil, nil) - p.declare(types.NewTypeName(pos, pkg, name, typ)) - - case typeTag: - p.typ(nil, nil) - - case varTag: - pos := p.pos() - pkg, name := p.qualifiedName() - typ := p.typ(nil, nil) - p.declare(types.NewVar(pos, pkg, name, typ)) - - case funcTag: - pos := p.pos() - pkg, name := p.qualifiedName() - params, isddd := p.paramList() - result, _ := p.paramList() - sig := types.NewSignature(nil, params, result, isddd) - p.declare(types.NewFunc(pos, pkg, name, sig)) - - default: - errorf("unexpected object tag %d", tag) - } -} - -const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go - -func (p *importer) pos() token.Pos { - if !p.posInfoFormat { - return token.NoPos - } - - file := p.prevFile - line := p.prevLine - delta := p.int() - line += delta - if p.version >= 5 { - if delta == deltaNewFile { - if n := p.int(); n >= 0 { - // file changed - file = p.path() - line = n - } - } - } else { - if delta == 0 { - if n := p.int(); n >= 0 { - // file changed - file = p.prevFile[:n] + p.string() - line = p.int() - } - } - } - p.prevFile = file - p.prevLine = line - - return p.fake.pos(file, line, 0) -} - -// Synthesize a token.Pos -type fakeFileSet struct { - fset *token.FileSet - files map[string]*token.File -} - -func (s *fakeFileSet) pos(file string, line, column int) token.Pos { - // TODO(mdempsky): Make use of column. - - // Since we don't know the set of needed file positions, we - // reserve maxlines positions per file. - const maxlines = 64 * 1024 - f := s.files[file] - if f == nil { - f = s.fset.AddFile(file, -1, maxlines) - s.files[file] = f - // Allocate the fake linebreak indices on first use. - // TODO(adonovan): opt: save ~512KB using a more complex scheme? - fakeLinesOnce.Do(func() { - fakeLines = make([]int, maxlines) - for i := range fakeLines { - fakeLines[i] = i - } - }) - f.SetLines(fakeLines) - } - - if line > maxlines { - line = 1 - } - - // Treat the file as if it contained only newlines - // and column=1: use the line number as the offset. - return f.Pos(line - 1) -} - -var ( - fakeLines []int - fakeLinesOnce sync.Once -) - -func (p *importer) qualifiedName() (pkg *types.Package, name string) { - name = p.string() - pkg = p.pkg() - return -} - -func (p *importer) record(t types.Type) { - p.typList = append(p.typList, t) -} - -// A dddSlice is a types.Type representing ...T parameters. -// It only appears for parameter types and does not escape -// the importer. -type dddSlice struct { - elem types.Type -} - -func (t *dddSlice) Underlying() types.Type { return t } -func (t *dddSlice) String() string { return "..." + t.elem.String() } - -// parent is the package which declared the type; parent == nil means -// the package currently imported. The parent package is needed for -// exported struct fields and interface methods which don't contain -// explicit package information in the export data. -// -// A non-nil tname is used as the "owner" of the result type; i.e., -// the result type is the underlying type of tname. tname is used -// to give interface methods a named receiver type where possible. -func (p *importer) typ(parent *types.Package, tname *types.Named) types.Type { - // if the type was seen before, i is its index (>= 0) - i := p.tagOrIndex() - if i >= 0 { - return p.typList[i] - } - - // otherwise, i is the type tag (< 0) - switch i { - case namedTag: - // read type object - pos := p.pos() - parent, name := p.qualifiedName() - scope := parent.Scope() - obj := scope.Lookup(name) - - // if the object doesn't exist yet, create and insert it - if obj == nil { - obj = types.NewTypeName(pos, parent, name, nil) - scope.Insert(obj) - } - - if _, ok := obj.(*types.TypeName); !ok { - errorf("pkg = %s, name = %s => %s", parent, name, obj) - } - - // associate new named type with obj if it doesn't exist yet - t0 := types.NewNamed(obj.(*types.TypeName), nil, nil) - - // but record the existing type, if any - tname := obj.Type().(*types.Named) // tname is either t0 or the existing type - p.record(tname) - - // read underlying type - t0.SetUnderlying(p.typ(parent, t0)) - - // interfaces don't have associated methods - if types.IsInterface(t0) { - return tname - } - - // read associated methods - for i := p.int(); i > 0; i-- { - // TODO(gri) replace this with something closer to fieldName - pos := p.pos() - name := p.string() - if !exported(name) { - p.pkg() - } - - recv, _ := p.paramList() // TODO(gri) do we need a full param list for the receiver? - params, isddd := p.paramList() - result, _ := p.paramList() - p.int() // go:nointerface pragma - discarded - - sig := types.NewSignature(recv.At(0), params, result, isddd) - t0.AddMethod(types.NewFunc(pos, parent, name, sig)) - } - - return tname - - case arrayTag: - t := new(types.Array) - if p.trackAllTypes { - p.record(t) - } - - n := p.int64() - *t = *types.NewArray(p.typ(parent, nil), n) - return t - - case sliceTag: - t := new(types.Slice) - if p.trackAllTypes { - p.record(t) - } - - *t = *types.NewSlice(p.typ(parent, nil)) - return t - - case dddTag: - t := new(dddSlice) - if p.trackAllTypes { - p.record(t) - } - - t.elem = p.typ(parent, nil) - return t - - case structTag: - t := new(types.Struct) - if p.trackAllTypes { - p.record(t) - } - - *t = *types.NewStruct(p.fieldList(parent)) - return t - - case pointerTag: - t := new(types.Pointer) - if p.trackAllTypes { - p.record(t) - } - - *t = *types.NewPointer(p.typ(parent, nil)) - return t - - case signatureTag: - t := new(types.Signature) - if p.trackAllTypes { - p.record(t) - } - - params, isddd := p.paramList() - result, _ := p.paramList() - *t = *types.NewSignature(nil, params, result, isddd) - return t - - case interfaceTag: - // Create a dummy entry in the type list. This is safe because we - // cannot expect the interface type to appear in a cycle, as any - // such cycle must contain a named type which would have been - // first defined earlier. - // TODO(gri) Is this still true now that we have type aliases? - // See issue #23225. - n := len(p.typList) - if p.trackAllTypes { - p.record(nil) - } - - var embeddeds []types.Type - for n := p.int(); n > 0; n-- { - p.pos() - embeddeds = append(embeddeds, p.typ(parent, nil)) - } - - t := newInterface(p.methodList(parent, tname), embeddeds) - p.interfaceList = append(p.interfaceList, t) - if p.trackAllTypes { - p.typList[n] = t - } - return t - - case mapTag: - t := new(types.Map) - if p.trackAllTypes { - p.record(t) - } - - key := p.typ(parent, nil) - val := p.typ(parent, nil) - *t = *types.NewMap(key, val) - return t - - case chanTag: - t := new(types.Chan) - if p.trackAllTypes { - p.record(t) - } - - dir := chanDir(p.int()) - val := p.typ(parent, nil) - *t = *types.NewChan(dir, val) - return t - - default: - errorf("unexpected type tag %d", i) // panics - panic("unreachable") - } -} - -func chanDir(d int) types.ChanDir { - // tag values must match the constants in cmd/compile/internal/gc/go.go - switch d { - case 1 /* Crecv */ : - return types.RecvOnly - case 2 /* Csend */ : - return types.SendOnly - case 3 /* Cboth */ : - return types.SendRecv - default: - errorf("unexpected channel dir %d", d) - return 0 - } -} - -func (p *importer) fieldList(parent *types.Package) (fields []*types.Var, tags []string) { - if n := p.int(); n > 0 { - fields = make([]*types.Var, n) - tags = make([]string, n) - for i := range fields { - fields[i], tags[i] = p.field(parent) - } - } - return -} - -func (p *importer) field(parent *types.Package) (*types.Var, string) { - pos := p.pos() - pkg, name, alias := p.fieldName(parent) - typ := p.typ(parent, nil) - tag := p.string() - - anonymous := false - if name == "" { - // anonymous field - typ must be T or *T and T must be a type name - switch typ := deref(typ).(type) { - case *types.Basic: // basic types are named types - pkg = nil // // objects defined in Universe scope have no package - name = typ.Name() - case *types.Named: - name = typ.Obj().Name() - default: - errorf("named base type expected") - } - anonymous = true - } else if alias { - // anonymous field: we have an explicit name because it's an alias - anonymous = true - } - - return types.NewField(pos, pkg, name, typ, anonymous), tag -} - -func (p *importer) methodList(parent *types.Package, baseType *types.Named) (methods []*types.Func) { - if n := p.int(); n > 0 { - methods = make([]*types.Func, n) - for i := range methods { - methods[i] = p.method(parent, baseType) - } - } - return -} - -func (p *importer) method(parent *types.Package, baseType *types.Named) *types.Func { - pos := p.pos() - pkg, name, _ := p.fieldName(parent) - // If we don't have a baseType, use a nil receiver. - // A receiver using the actual interface type (which - // we don't know yet) will be filled in when we call - // types.Interface.Complete. - var recv *types.Var - if baseType != nil { - recv = types.NewVar(token.NoPos, parent, "", baseType) - } - params, isddd := p.paramList() - result, _ := p.paramList() - sig := types.NewSignature(recv, params, result, isddd) - return types.NewFunc(pos, pkg, name, sig) -} - -func (p *importer) fieldName(parent *types.Package) (pkg *types.Package, name string, alias bool) { - name = p.string() - pkg = parent - if pkg == nil { - // use the imported package instead - pkg = p.pkgList[0] - } - if p.version == 0 && name == "_" { - // version 0 didn't export a package for _ fields - return - } - switch name { - case "": - // 1) field name matches base type name and is exported: nothing to do - case "?": - // 2) field name matches base type name and is not exported: need package - name = "" - pkg = p.pkg() - case "@": - // 3) field name doesn't match type name (alias) - name = p.string() - alias = true - fallthrough - default: - if !exported(name) { - pkg = p.pkg() - } - } - return -} - -func (p *importer) paramList() (*types.Tuple, bool) { - n := p.int() - if n == 0 { - return nil, false - } - // negative length indicates unnamed parameters - named := true - if n < 0 { - n = -n - named = false - } - // n > 0 - params := make([]*types.Var, n) - isddd := false - for i := range params { - params[i], isddd = p.param(named) - } - return types.NewTuple(params...), isddd -} - -func (p *importer) param(named bool) (*types.Var, bool) { - t := p.typ(nil, nil) - td, isddd := t.(*dddSlice) - if isddd { - t = types.NewSlice(td.elem) - } - - var pkg *types.Package - var name string - if named { - name = p.string() - if name == "" { - errorf("expected named parameter") - } - if name != "_" { - pkg = p.pkg() - } - if i := strings.Index(name, "·"); i > 0 { - name = name[:i] // cut off gc-specific parameter numbering - } - } - - // read and discard compiler-specific info - p.string() - - return types.NewVar(token.NoPos, pkg, name, t), isddd -} - -func exported(name string) bool { - ch, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(ch) -} - -func (p *importer) value() constant.Value { - switch tag := p.tagOrIndex(); tag { - case falseTag: - return constant.MakeBool(false) - case trueTag: - return constant.MakeBool(true) - case int64Tag: - return constant.MakeInt64(p.int64()) - case floatTag: - return p.float() - case complexTag: - re := p.float() - im := p.float() - return constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) - case stringTag: - return constant.MakeString(p.string()) - case unknownTag: - return constant.MakeUnknown() - default: - errorf("unexpected value tag %d", tag) // panics - panic("unreachable") - } -} - -func (p *importer) float() constant.Value { - sign := p.int() - if sign == 0 { - return constant.MakeInt64(0) - } - - exp := p.int() - mant := []byte(p.string()) // big endian - - // remove leading 0's if any - for len(mant) > 0 && mant[0] == 0 { - mant = mant[1:] - } - - // convert to little endian - // TODO(gri) go/constant should have a more direct conversion function - // (e.g., once it supports a big.Float based implementation) - for i, j := 0, len(mant)-1; i < j; i, j = i+1, j-1 { - mant[i], mant[j] = mant[j], mant[i] - } - - // adjust exponent (constant.MakeFromBytes creates an integer value, - // but mant represents the mantissa bits such that 0.5 <= mant < 1.0) - exp -= len(mant) << 3 - if len(mant) > 0 { - for msd := mant[len(mant)-1]; msd&0x80 == 0; msd <<= 1 { - exp++ - } - } - - x := constant.MakeFromBytes(mant) - switch { - case exp < 0: - d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) - x = constant.BinaryOp(x, token.QUO, d) - case exp > 0: - x = constant.Shift(x, token.SHL, uint(exp)) - } - - if sign < 0 { - x = constant.UnaryOp(token.SUB, x, 0) - } - return x -} - -// ---------------------------------------------------------------------------- -// Low-level decoders - -func (p *importer) tagOrIndex() int { - if p.debugFormat { - p.marker('t') - } - - return int(p.rawInt64()) -} - -func (p *importer) int() int { - x := p.int64() - if int64(int(x)) != x { - errorf("exported integer too large") - } - return int(x) -} - -func (p *importer) int64() int64 { - if p.debugFormat { - p.marker('i') - } - - return p.rawInt64() -} - -func (p *importer) path() string { - if p.debugFormat { - p.marker('p') - } - // if the path was seen before, i is its index (>= 0) - // (the empty string is at index 0) - i := p.rawInt64() - if i >= 0 { - return p.pathList[i] - } - // otherwise, i is the negative path length (< 0) - a := make([]string, -i) - for n := range a { - a[n] = p.string() - } - s := strings.Join(a, "/") - p.pathList = append(p.pathList, s) - return s -} - -func (p *importer) string() string { - if p.debugFormat { - p.marker('s') - } - // if the string was seen before, i is its index (>= 0) - // (the empty string is at index 0) - i := p.rawInt64() - if i >= 0 { - return p.strList[i] - } - // otherwise, i is the negative string length (< 0) - if n := int(-i); n <= cap(p.buf) { - p.buf = p.buf[:n] - } else { - p.buf = make([]byte, n) - } - for i := range p.buf { - p.buf[i] = p.rawByte() - } - s := string(p.buf) - p.strList = append(p.strList, s) - return s -} - -func (p *importer) marker(want byte) { - if got := p.rawByte(); got != want { - errorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read) - } - - pos := p.read - if n := int(p.rawInt64()); n != pos { - errorf("incorrect position: got %d; want %d", n, pos) - } -} - -// rawInt64 should only be used by low-level decoders. -func (p *importer) rawInt64() int64 { - i, err := binary.ReadVarint(p) - if err != nil { - errorf("read error: %v", err) - } - return i -} - -// rawStringln should only be used to read the initial version string. -func (p *importer) rawStringln(b byte) string { - p.buf = p.buf[:0] - for b != '\n' { - p.buf = append(p.buf, b) - b = p.rawByte() - } - return string(p.buf) -} - -// needed for binary.ReadVarint in rawInt64 -func (p *importer) ReadByte() (byte, error) { - return p.rawByte(), nil -} - -// byte is the bottleneck interface for reading p.data. -// It unescapes '|' 'S' to '$' and '|' '|' to '|'. -// rawByte should only be used by low-level decoders. -func (p *importer) rawByte() byte { - b := p.data[0] - r := 1 - if b == '|' { - b = p.data[1] - r = 2 - switch b { - case 'S': - b = '$' - case '|': - // nothing to do - default: - errorf("unexpected escape sequence in export data") - } - } - p.data = p.data[r:] - p.read += r - return b - -} - -// ---------------------------------------------------------------------------- -// Export format - -// Tags. Must be < 0. -const ( - // Objects - packageTag = -(iota + 1) - constTag - typeTag - varTag - funcTag - endTag - - // Types - namedTag - arrayTag - sliceTag - dddTag - structTag - pointerTag - signatureTag - interfaceTag - mapTag - chanTag - - // Values - falseTag - trueTag - int64Tag - floatTag - fractionTag // not used by gc - complexTag - stringTag - nilTag // only used by gc (appears in exported inlined function bodies) - unknownTag // not used by gc (only appears in packages with errors) - - // Type aliases - aliasTag -) - -var predeclOnce sync.Once -var predecl []types.Type // initialized lazily - -func predeclared() []types.Type { - predeclOnce.Do(func() { - // initialize lazily to be sure that all - // elements have been initialized before - predecl = []types.Type{ // basic types - types.Typ[types.Bool], - types.Typ[types.Int], - types.Typ[types.Int8], - types.Typ[types.Int16], - types.Typ[types.Int32], - types.Typ[types.Int64], - types.Typ[types.Uint], - types.Typ[types.Uint8], - types.Typ[types.Uint16], - types.Typ[types.Uint32], - types.Typ[types.Uint64], - types.Typ[types.Uintptr], - types.Typ[types.Float32], - types.Typ[types.Float64], - types.Typ[types.Complex64], - types.Typ[types.Complex128], - types.Typ[types.String], - - // basic type aliases - types.Universe.Lookup("byte").Type(), - types.Universe.Lookup("rune").Type(), - - // error - types.Universe.Lookup("error").Type(), - - // untyped types - types.Typ[types.UntypedBool], - types.Typ[types.UntypedInt], - types.Typ[types.UntypedRune], - types.Typ[types.UntypedFloat], - types.Typ[types.UntypedComplex], - types.Typ[types.UntypedString], - types.Typ[types.UntypedNil], - - // package unsafe - types.Typ[types.UnsafePointer], - - // invalid type - types.Typ[types.Invalid], // only appears in packages with errors - - // used internally by gc; never used by this package or in .a files - anyType{}, - } - }) - return predecl -} - -type anyType struct{} - -func (t anyType) Underlying() types.Type { return t } -func (t anyType) String() string { return "any" } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go index 9cf186605f..6a9265ea94 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go @@ -204,14 +204,11 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func // Or, define a new standard go/types/gcexportdata package. fset := token.NewFileSet() - // The indexed export format starts with an 'i'; the older - // binary export format starts with a 'c', 'd', or 'v' - // (from "version"). Select appropriate importer. - if len(data) > 0 && data[0] == 'i' { - _, pkg, err = IImportData(fset, packages, data[1:], id) - } else { - _, pkg, err = BImportData(fset, packages, data, id) + // The indexed export format starts with an 'i'. + if len(data) == 0 || data[0] != 'i' { + return nil, fmt.Errorf("unknown export data format") } + _, pkg, err = IImportData(fset, packages, data[1:], id) default: err = fmt.Errorf("unknown export data header: %q", hdr) @@ -344,7 +341,7 @@ func (p *parser) expectKeyword(keyword string) { // PackageId = string_lit . // -func (p *parser) parsePackageId() string { +func (p *parser) parsePackageID() string { id, err := strconv.Unquote(p.expect(scanner.String)) if err != nil { p.error(err) @@ -384,7 +381,7 @@ func (p *parser) parseDotIdent() string { // func (p *parser) parseQualifiedName() (id, name string) { p.expect('@') - id = p.parsePackageId() + id = p.parsePackageID() p.expect('.') // Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields. if p.tok == '?' { @@ -696,7 +693,7 @@ func (p *parser) parseInterfaceType(parent *types.Package) types.Type { // Complete requires the type's embedded interfaces to be fully defined, // but we do not define any - return types.NewInterface(methods, nil).Complete() + return newInterface(methods, nil).Complete() } // ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . @@ -785,7 +782,7 @@ func (p *parser) parseType(parent *types.Package) types.Type { func (p *parser) parseImportDecl() { p.expectKeyword("import") name := p.parsePackageName() - p.getPkg(p.parsePackageId(), name) + p.getPkg(p.parsePackageID(), name) } // int_lit = [ "+" | "-" ] { "0" ... "9" } . diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go index 4be32a2e55..858eb9f456 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go @@ -11,6 +11,7 @@ package gcimporter import ( "bytes" "encoding/binary" + "fmt" "go/ast" "go/constant" "go/token" @@ -25,6 +26,15 @@ import ( // 0: Go1.11 encoding const iexportVersion = 0 +// internalError represents an error generated inside this package. +type internalError string + +func (e internalError) Error() string { return "gcimporter: " + string(e) } + +func internalErrorf(format string, args ...interface{}) error { + return internalError(fmt.Sprintf(format, args...)) +} + // IExportData returns the binary export data for pkg. // // If no file set is provided, position info will be missing. @@ -528,6 +538,16 @@ func constantToFloat(x constant.Value) *big.Float { return &f } +func valueToRat(x constant.Value) *big.Rat { + // Convert little-endian to big-endian. + // I can't believe this is necessary. + bytes := constant.Bytes(x) + for i := 0; i < len(bytes)/2; i++ { + bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i] + } + return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes)) +} + // mpint exports a multi-precision integer. // // For unsigned types, small values are written out as a single diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go index a31a880263..fef8b30080 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go @@ -18,6 +18,9 @@ import ( "go/types" "io" "sort" + "sync" + "unicode" + "unicode/utf8" ) type intReader struct { @@ -25,6 +28,10 @@ type intReader struct { path string } +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + func (r *intReader) int64() int64 { i, err := binary.ReadVarint(r.Reader) if err != nil { @@ -628,3 +635,166 @@ func (r *importReader) byte() byte { } return x } + +const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go + +// Synthesize a token.Pos +type fakeFileSet struct { + fset *token.FileSet + files map[string]*token.File +} + +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + + // Since we don't know the set of needed file positions, we + // reserve maxlines positions per file. + const maxlines = 64 * 1024 + f := s.files[file] + if f == nil { + f = s.fset.AddFile(file, -1, maxlines) + s.files[file] = f + // Allocate the fake linebreak indices on first use. + // TODO(adonovan): opt: save ~512KB using a more complex scheme? + fakeLinesOnce.Do(func() { + fakeLines = make([]int, maxlines) + for i := range fakeLines { + fakeLines[i] = i + } + }) + f.SetLines(fakeLines) + } + + if line > maxlines { + line = 1 + } + + // Treat the file as if it contained only newlines + // and column=1: use the line number as the offset. + return f.Pos(line - 1) +} + +var ( + fakeLines []int + fakeLinesOnce sync.Once +) + +func chanDir(d int) types.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types.RecvOnly + case 2 /* Csend */ : + return types.SendOnly + case 3 /* Cboth */ : + return types.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +func exported(name string) bool { + ch, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(ch) +} + +// ---------------------------------------------------------------------------- +// Export format + +// Tags. Must be < 0. +const ( + // Objects + packageTag = -(iota + 1) + constTag + typeTag + varTag + funcTag + endTag + + // Types + namedTag + arrayTag + sliceTag + dddTag + structTag + pointerTag + signatureTag + interfaceTag + mapTag + chanTag + + // Values + falseTag + trueTag + int64Tag + floatTag + fractionTag // not used by gc + complexTag + stringTag + nilTag // only used by gc (appears in exported inlined function bodies) + unknownTag // not used by gc (only appears in packages with errors) + + // Type aliases + aliasTag +) + +var predeclOnce sync.Once +var predecl []types.Type // initialized lazily + +func predeclared() []types.Type { + predeclOnce.Do(func() { + // initialize lazily to be sure that all + // elements have been initialized before + predecl = []types.Type{ // basic types + types.Typ[types.Bool], + types.Typ[types.Int], + types.Typ[types.Int8], + types.Typ[types.Int16], + types.Typ[types.Int32], + types.Typ[types.Int64], + types.Typ[types.Uint], + types.Typ[types.Uint8], + types.Typ[types.Uint16], + types.Typ[types.Uint32], + types.Typ[types.Uint64], + types.Typ[types.Uintptr], + types.Typ[types.Float32], + types.Typ[types.Float64], + types.Typ[types.Complex64], + types.Typ[types.Complex128], + types.Typ[types.String], + + // basic type aliases + types.Universe.Lookup("byte").Type(), + types.Universe.Lookup("rune").Type(), + + // error + types.Universe.Lookup("error").Type(), + + // untyped types + types.Typ[types.UntypedBool], + types.Typ[types.UntypedInt], + types.Typ[types.UntypedRune], + types.Typ[types.UntypedFloat], + types.Typ[types.UntypedComplex], + types.Typ[types.UntypedString], + types.Typ[types.UntypedNil], + + // package unsafe + types.Typ[types.UnsafePointer], + + // invalid type + types.Typ[types.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, + } + }) + return predecl +} + +type anyType struct{} + +func (t anyType) Underlying() types.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go index db0c9a7ea6..dc6177c122 100644 --- a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -11,17 +11,15 @@ import ( "encoding/json" "fmt" "go/types" - "log" - "os" "os/exec" "strings" - "time" + + "golang.org/x/tools/internal/gocommand" ) var debug = false -// GetSizes returns the sizes used by the underlying driver with the given parameters. -func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { +func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { // TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. const toolPrefix = "GOPACKAGESDRIVER=" tool := "" @@ -41,7 +39,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp } if tool == "off" { - return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) + return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir) } req, err := json.Marshal(struct { @@ -77,98 +75,43 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp return response.Sizes, nil } -func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { - args := []string{"list", "-f", "{{context.GOARCH}} {{context.Compiler}}"} - args = append(args, buildFlags...) - args = append(args, "--", "unsafe") - stdout, stderr, err := invokeGo(ctx, env, dir, usesExportData, args...) +func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { + inv := gocommand.Invocation{ + Verb: "list", + Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}, + Env: env, + BuildFlags: buildFlags, + WorkingDir: dir, + } + stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) var goarch, compiler string - if err != nil { - if strings.Contains(err.Error(), "cannot find main module") { + if rawErr != nil { + if strings.Contains(rawErr.Error(), "cannot find main module") { // User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc. // TODO(matloob): Is this a problem in practice? - envout, _, enverr := invokeGo(ctx, env, dir, usesExportData, "env", "GOARCH") + inv := gocommand.Invocation{ + Verb: "env", + Args: []string{"GOARCH"}, + Env: env, + WorkingDir: dir, + } + envout, enverr := gocmdRunner.Run(ctx, inv) if enverr != nil { - return nil, err + return nil, enverr } goarch = strings.TrimSpace(envout.String()) compiler = "gc" } else { - return nil, err + return nil, friendlyErr } } else { fields := strings.Fields(stdout.String()) if len(fields) < 2 { - return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \" \" from stdout of go command:\n%s\ndir: %s\nstdout: <<%s>>\nstderr: <<%s>>", - cmdDebugStr(env, args...), dir, stdout.String(), stderr.String()) + return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \" \":\nstdout: <<%s>>\nstderr: <<%s>>", + stdout.String(), stderr.String()) } goarch = fields[0] compiler = fields[1] } return types.SizesFor(compiler, goarch), nil } - -// invokeGo returns the stdout and stderr of a go command invocation. -func invokeGo(ctx context.Context, env []string, dir string, usesExportData bool, args ...string) (*bytes.Buffer, *bytes.Buffer, error) { - if debug { - defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(env, args...)) }(time.Now()) - } - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd := exec.CommandContext(ctx, "go", args...) - // On darwin the cwd gets resolved to the real path, which breaks anything that - // expects the working directory to keep the original path, including the - // go command when dealing with modules. - // The Go stdlib has a special feature where if the cwd and the PWD are the - // same node then it trusts the PWD, so by setting it in the env for the child - // process we fix up all the paths returned by the go command. - cmd.Env = append(append([]string{}, env...), "PWD="+dir) - cmd.Dir = dir - cmd.Stdout = stdout - cmd.Stderr = stderr - if err := cmd.Run(); err != nil { - exitErr, ok := err.(*exec.ExitError) - if !ok { - // Catastrophic error: - // - executable not found - // - context cancellation - return nil, nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) - } - - // Export mode entails a build. - // If that build fails, errors appear on stderr - // (despite the -e flag) and the Export field is blank. - // Do not fail in that case. - if !usesExportData { - return nil, nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) - } - } - - // As of writing, go list -export prints some non-fatal compilation - // errors to stderr, even with -e set. We would prefer that it put - // them in the Package.Error JSON (see https://golang.org/issue/26319). - // In the meantime, there's nowhere good to put them, but they can - // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS - // is set. - if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { - fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(env, args...), stderr) - } - - // debugging - if false { - fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(env, args...), stdout) - } - - return stdout, stderr, nil -} - -func cmdDebugStr(envlist []string, args ...string) string { - env := make(map[string]string) - for _, kv := range envlist { - split := strings.Split(kv, "=") - k, v := split[0], split[1] - env[k] = v - } - - return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args) -} diff --git a/vendor/golang.org/x/tools/go/loader/doc.go b/vendor/golang.org/x/tools/go/loader/doc.go new file mode 100644 index 0000000000..c5aa31c1a0 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/doc.go @@ -0,0 +1,204 @@ +// Copyright 2015 The Go 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 loader loads a complete Go program from source code, parsing +// and type-checking the initial packages plus their transitive closure +// of dependencies. The ASTs and the derived facts are retained for +// later use. +// +// Deprecated: This is an older API and does not have support +// for modules. Use golang.org/x/tools/go/packages instead. +// +// The package defines two primary types: Config, which specifies a +// set of initial packages to load and various other options; and +// Program, which is the result of successfully loading the packages +// specified by a configuration. +// +// The configuration can be set directly, but *Config provides various +// convenience methods to simplify the common cases, each of which can +// be called any number of times. Finally, these are followed by a +// call to Load() to actually load and type-check the program. +// +// var conf loader.Config +// +// // Use the command-line arguments to specify +// // a set of initial packages to load from source. +// // See FromArgsUsage for help. +// rest, err := conf.FromArgs(os.Args[1:], wantTests) +// +// // Parse the specified files and create an ad hoc package with path "foo". +// // All files must have the same 'package' declaration. +// conf.CreateFromFilenames("foo", "foo.go", "bar.go") +// +// // Create an ad hoc package with path "foo" from +// // the specified already-parsed files. +// // All ASTs must have the same 'package' declaration. +// conf.CreateFromFiles("foo", parsedFiles) +// +// // Add "runtime" to the set of packages to be loaded. +// conf.Import("runtime") +// +// // Adds "fmt" and "fmt_test" to the set of packages +// // to be loaded. "fmt" will include *_test.go files. +// conf.ImportWithTests("fmt") +// +// // Finally, load all the packages specified by the configuration. +// prog, err := conf.Load() +// +// See examples_test.go for examples of API usage. +// +// +// CONCEPTS AND TERMINOLOGY +// +// The WORKSPACE is the set of packages accessible to the loader. The +// workspace is defined by Config.Build, a *build.Context. The +// default context treats subdirectories of $GOROOT and $GOPATH as +// packages, but this behavior may be overridden. +// +// An AD HOC package is one specified as a set of source files on the +// command line. In the simplest case, it may consist of a single file +// such as $GOROOT/src/net/http/triv.go. +// +// EXTERNAL TEST packages are those comprised of a set of *_test.go +// files all with the same 'package foo_test' declaration, all in the +// same directory. (go/build.Package calls these files XTestFiles.) +// +// An IMPORTABLE package is one that can be referred to by some import +// spec. Every importable package is uniquely identified by its +// PACKAGE PATH or just PATH, a string such as "fmt", "encoding/json", +// or "cmd/vendor/golang.org/x/arch/x86/x86asm". A package path +// typically denotes a subdirectory of the workspace. +// +// An import declaration uses an IMPORT PATH to refer to a package. +// Most import declarations use the package path as the import path. +// +// Due to VENDORING (https://golang.org/s/go15vendor), the +// interpretation of an import path may depend on the directory in which +// it appears. To resolve an import path to a package path, go/build +// must search the enclosing directories for a subdirectory named +// "vendor". +// +// ad hoc packages and external test packages are NON-IMPORTABLE. The +// path of an ad hoc package is inferred from the package +// declarations of its files and is therefore not a unique package key. +// For example, Config.CreatePkgs may specify two initial ad hoc +// packages, both with path "main". +// +// An AUGMENTED package is an importable package P plus all the +// *_test.go files with same 'package foo' declaration as P. +// (go/build.Package calls these files TestFiles.) +// +// The INITIAL packages are those specified in the configuration. A +// DEPENDENCY is a package loaded to satisfy an import in an initial +// package or another dependency. +// +package loader + +// IMPLEMENTATION NOTES +// +// 'go test', in-package test files, and import cycles +// --------------------------------------------------- +// +// An external test package may depend upon members of the augmented +// package that are not in the unaugmented package, such as functions +// that expose internals. (See bufio/export_test.go for an example.) +// So, the loader must ensure that for each external test package +// it loads, it also augments the corresponding non-test package. +// +// The import graph over n unaugmented packages must be acyclic; the +// import graph over n-1 unaugmented packages plus one augmented +// package must also be acyclic. ('go test' relies on this.) But the +// import graph over n augmented packages may contain cycles. +// +// First, all the (unaugmented) non-test packages and their +// dependencies are imported in the usual way; the loader reports an +// error if it detects an import cycle. +// +// Then, each package P for which testing is desired is augmented by +// the list P' of its in-package test files, by calling +// (*types.Checker).Files. This arrangement ensures that P' may +// reference definitions within P, but P may not reference definitions +// within P'. Furthermore, P' may import any other package, including +// ones that depend upon P, without an import cycle error. +// +// Consider two packages A and B, both of which have lists of +// in-package test files we'll call A' and B', and which have the +// following import graph edges: +// B imports A +// B' imports A +// A' imports B +// This last edge would be expected to create an error were it not +// for the special type-checking discipline above. +// Cycles of size greater than two are possible. For example: +// compress/bzip2/bzip2_test.go (package bzip2) imports "io/ioutil" +// io/ioutil/tempfile_test.go (package ioutil) imports "regexp" +// regexp/exec_test.go (package regexp) imports "compress/bzip2" +// +// +// Concurrency +// ----------- +// +// Let us define the import dependency graph as follows. Each node is a +// list of files passed to (Checker).Files at once. Many of these lists +// are the production code of an importable Go package, so those nodes +// are labelled by the package's path. The remaining nodes are +// ad hoc packages and lists of in-package *_test.go files that augment +// an importable package; those nodes have no label. +// +// The edges of the graph represent import statements appearing within a +// file. An edge connects a node (a list of files) to the node it +// imports, which is importable and thus always labelled. +// +// Loading is controlled by this dependency graph. +// +// To reduce I/O latency, we start loading a package's dependencies +// asynchronously as soon as we've parsed its files and enumerated its +// imports (scanImports). This performs a preorder traversal of the +// import dependency graph. +// +// To exploit hardware parallelism, we type-check unrelated packages in +// parallel, where "unrelated" means not ordered by the partial order of +// the import dependency graph. +// +// We use a concurrency-safe non-blocking cache (importer.imported) to +// record the results of type-checking, whether success or failure. An +// entry is created in this cache by startLoad the first time the +// package is imported. The first goroutine to request an entry becomes +// responsible for completing the task and broadcasting completion to +// subsequent requestors, which block until then. +// +// Type checking occurs in (parallel) postorder: we cannot type-check a +// set of files until we have loaded and type-checked all of their +// immediate dependencies (and thus all of their transitive +// dependencies). If the input were guaranteed free of import cycles, +// this would be trivial: we could simply wait for completion of the +// dependencies and then invoke the typechecker. +// +// But as we saw in the 'go test' section above, some cycles in the +// import graph over packages are actually legal, so long as the +// cycle-forming edge originates in the in-package test files that +// augment the package. This explains why the nodes of the import +// dependency graph are not packages, but lists of files: the unlabelled +// nodes avoid the cycles. Consider packages A and B where B imports A +// and A's in-package tests AT import B. The naively constructed import +// graph over packages would contain a cycle (A+AT) --> B --> (A+AT) but +// the graph over lists of files is AT --> B --> A, where AT is an +// unlabelled node. +// +// Awaiting completion of the dependencies in a cyclic graph would +// deadlock, so we must materialize the import dependency graph (as +// importer.graph) and check whether each import edge forms a cycle. If +// x imports y, and the graph already contains a path from y to x, then +// there is an import cycle, in which case the processing of x must not +// wait for the completion of processing of y. +// +// When the type-checker makes a callback (doImport) to the loader for a +// given import edge, there are two possible cases. In the normal case, +// the dependency has already been completely type-checked; doImport +// does a cache lookup and returns it. In the cyclic case, the entry in +// the cache is still necessarily incomplete, indicating a cycle. We +// perform the cycle check again to obtain the error message, and return +// the error. +// +// The result of using concurrency is about a 2.5x speedup for stdlib_test. diff --git a/vendor/golang.org/x/tools/go/loader/loader.go b/vendor/golang.org/x/tools/go/loader/loader.go new file mode 100644 index 0000000000..bc12ca33d1 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/loader.go @@ -0,0 +1,1086 @@ +// Copyright 2013 The Go 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 loader + +// See doc.go for package documentation and implementation notes. + +import ( + "errors" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/internal/cgo" +) + +var ignoreVendor build.ImportMode + +const trace = false // show timing info for type-checking + +// Config specifies the configuration for loading a whole program from +// Go source code. +// The zero value for Config is a ready-to-use default configuration. +type Config struct { + // Fset is the file set for the parser to use when loading the + // program. If nil, it may be lazily initialized by any + // method of Config. + Fset *token.FileSet + + // ParserMode specifies the mode to be used by the parser when + // loading source packages. + ParserMode parser.Mode + + // TypeChecker contains options relating to the type checker. + // + // The supplied IgnoreFuncBodies is not used; the effective + // value comes from the TypeCheckFuncBodies func below. + // The supplied Import function is not used either. + TypeChecker types.Config + + // TypeCheckFuncBodies is a predicate over package paths. + // A package for which the predicate is false will + // have its package-level declarations type checked, but not + // its function bodies; this can be used to quickly load + // dependencies from source. If nil, all func bodies are type + // checked. + TypeCheckFuncBodies func(path string) bool + + // If Build is non-nil, it is used to locate source packages. + // Otherwise &build.Default is used. + // + // By default, cgo is invoked to preprocess Go files that + // import the fake package "C". This behaviour can be + // disabled by setting CGO_ENABLED=0 in the environment prior + // to startup, or by setting Build.CgoEnabled=false. + Build *build.Context + + // The current directory, used for resolving relative package + // references such as "./go/loader". If empty, os.Getwd will be + // used instead. + Cwd string + + // If DisplayPath is non-nil, it is used to transform each + // file name obtained from Build.Import(). This can be used + // to prevent a virtualized build.Config's file names from + // leaking into the user interface. + DisplayPath func(path string) string + + // If AllowErrors is true, Load will return a Program even + // if some of the its packages contained I/O, parser or type + // errors; such errors are accessible via PackageInfo.Errors. If + // false, Load will fail if any package had an error. + AllowErrors bool + + // CreatePkgs specifies a list of non-importable initial + // packages to create. The resulting packages will appear in + // the corresponding elements of the Program.Created slice. + CreatePkgs []PkgSpec + + // ImportPkgs specifies a set of initial packages to load. + // The map keys are package paths. + // + // The map value indicates whether to load tests. If true, Load + // will add and type-check two lists of files to the package: + // non-test files followed by in-package *_test.go files. In + // addition, it will append the external test package (if any) + // to Program.Created. + ImportPkgs map[string]bool + + // FindPackage is called during Load to create the build.Package + // for a given import path from a given directory. + // If FindPackage is nil, (*build.Context).Import is used. + // A client may use this hook to adapt to a proprietary build + // system that does not follow the "go build" layout + // conventions, for example. + // + // It must be safe to call concurrently from multiple goroutines. + FindPackage func(ctxt *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error) + + // AfterTypeCheck is called immediately after a list of files + // has been type-checked and appended to info.Files. + // + // This optional hook function is the earliest opportunity for + // the client to observe the output of the type checker, + // which may be useful to reduce analysis latency when loading + // a large program. + // + // The function is permitted to modify info.Info, for instance + // to clear data structures that are no longer needed, which can + // dramatically reduce peak memory consumption. + // + // The function may be called twice for the same PackageInfo: + // once for the files of the package and again for the + // in-package test files. + // + // It must be safe to call concurrently from multiple goroutines. + AfterTypeCheck func(info *PackageInfo, files []*ast.File) +} + +// A PkgSpec specifies a non-importable package to be created by Load. +// Files are processed first, but typically only one of Files and +// Filenames is provided. The path needn't be globally unique. +// +// For vendoring purposes, the package's directory is the one that +// contains the first file. +type PkgSpec struct { + Path string // package path ("" => use package declaration) + Files []*ast.File // ASTs of already-parsed files + Filenames []string // names of files to be parsed +} + +// A Program is a Go program loaded from source as specified by a Config. +type Program struct { + Fset *token.FileSet // the file set for this program + + // Created[i] contains the initial package whose ASTs or + // filenames were supplied by Config.CreatePkgs[i], followed by + // the external test package, if any, of each package in + // Config.ImportPkgs ordered by ImportPath. + // + // NOTE: these files must not import "C". Cgo preprocessing is + // only performed on imported packages, not ad hoc packages. + // + // TODO(adonovan): we need to copy and adapt the logic of + // goFilesPackage (from $GOROOT/src/cmd/go/build.go) and make + // Config.Import and Config.Create methods return the same kind + // of entity, essentially a build.Package. + // Perhaps we can even reuse that type directly. + Created []*PackageInfo + + // Imported contains the initially imported packages, + // as specified by Config.ImportPkgs. + Imported map[string]*PackageInfo + + // AllPackages contains the PackageInfo of every package + // encountered by Load: all initial packages and all + // dependencies, including incomplete ones. + AllPackages map[*types.Package]*PackageInfo + + // importMap is the canonical mapping of package paths to + // packages. It contains all Imported initial packages, but not + // Created ones, and all imported dependencies. + importMap map[string]*types.Package +} + +// PackageInfo holds the ASTs and facts derived by the type-checker +// for a single package. +// +// Not mutated once exposed via the API. +// +type PackageInfo struct { + Pkg *types.Package + Importable bool // true if 'import "Pkg.Path()"' would resolve to this + TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors + Files []*ast.File // syntax trees for the package's files + Errors []error // non-nil if the package had errors + types.Info // type-checker deductions. + dir string // package directory + + checker *types.Checker // transient type-checker state + errorFunc func(error) +} + +func (info *PackageInfo) String() string { return info.Pkg.Path() } + +func (info *PackageInfo) appendError(err error) { + if info.errorFunc != nil { + info.errorFunc(err) + } else { + fmt.Fprintln(os.Stderr, err) + } + info.Errors = append(info.Errors, err) +} + +func (conf *Config) fset() *token.FileSet { + if conf.Fset == nil { + conf.Fset = token.NewFileSet() + } + return conf.Fset +} + +// ParseFile is a convenience function (intended for testing) that invokes +// the parser using the Config's FileSet, which is initialized if nil. +// +// src specifies the parser input as a string, []byte, or io.Reader, and +// filename is its apparent name. If src is nil, the contents of +// filename are read from the file system. +// +func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) { + // TODO(adonovan): use conf.build() etc like parseFiles does. + return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode) +} + +// FromArgsUsage is a partial usage message that applications calling +// FromArgs may wish to include in their -help output. +const FromArgsUsage = ` + is a list of arguments denoting a set of initial packages. +It may take one of two forms: + +1. A list of *.go source files. + + All of the specified files are loaded, parsed and type-checked + as a single package. All the files must belong to the same directory. + +2. A list of import paths, each denoting a package. + + The package's directory is found relative to the $GOROOT and + $GOPATH using similar logic to 'go build', and the *.go files in + that directory are loaded, parsed and type-checked as a single + package. + + In addition, all *_test.go files in the directory are then loaded + and parsed. Those files whose package declaration equals that of + the non-*_test.go files are included in the primary package. Test + files whose package declaration ends with "_test" are type-checked + as another package, the 'external' test package, so that a single + import path may denote two packages. (Whether this behaviour is + enabled is tool-specific, and may depend on additional flags.) + +A '--' argument terminates the list of packages. +` + +// FromArgs interprets args as a set of initial packages to load from +// source and updates the configuration. It returns the list of +// unconsumed arguments. +// +// It is intended for use in command-line interfaces that require a +// set of initial packages to be specified; see FromArgsUsage message +// for details. +// +// Only superficial errors are reported at this stage; errors dependent +// on I/O are detected during Load. +// +func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) { + var rest []string + for i, arg := range args { + if arg == "--" { + rest = args[i+1:] + args = args[:i] + break // consume "--" and return the remaining args + } + } + + if len(args) > 0 && strings.HasSuffix(args[0], ".go") { + // Assume args is a list of a *.go files + // denoting a single ad hoc package. + for _, arg := range args { + if !strings.HasSuffix(arg, ".go") { + return nil, fmt.Errorf("named files must be .go files: %s", arg) + } + } + conf.CreateFromFilenames("", args...) + } else { + // Assume args are directories each denoting a + // package and (perhaps) an external test, iff xtest. + for _, arg := range args { + if xtest { + conf.ImportWithTests(arg) + } else { + conf.Import(arg) + } + } + } + + return rest, nil +} + +// CreateFromFilenames is a convenience function that adds +// a conf.CreatePkgs entry to create a package of the specified *.go +// files. +// +func (conf *Config) CreateFromFilenames(path string, filenames ...string) { + conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames}) +} + +// CreateFromFiles is a convenience function that adds a conf.CreatePkgs +// entry to create package of the specified path and parsed files. +// +func (conf *Config) CreateFromFiles(path string, files ...*ast.File) { + conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files}) +} + +// ImportWithTests is a convenience function that adds path to +// ImportPkgs, the set of initial source packages located relative to +// $GOPATH. The package will be augmented by any *_test.go files in +// its directory that contain a "package x" (not "package x_test") +// declaration. +// +// In addition, if any *_test.go files contain a "package x_test" +// declaration, an additional package comprising just those files will +// be added to CreatePkgs. +// +func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) } + +// Import is a convenience function that adds path to ImportPkgs, the +// set of initial packages that will be imported from source. +// +func (conf *Config) Import(path string) { conf.addImport(path, false) } + +func (conf *Config) addImport(path string, tests bool) { + if path == "C" { + return // ignore; not a real package + } + if conf.ImportPkgs == nil { + conf.ImportPkgs = make(map[string]bool) + } + conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests +} + +// PathEnclosingInterval returns the PackageInfo and ast.Node that +// contain source interval [start, end), and all the node's ancestors +// up to the AST root. It searches all ast.Files of all packages in prog. +// exact is defined as for astutil.PathEnclosingInterval. +// +// The zero value is returned if not found. +// +func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { + for _, info := range prog.AllPackages { + for _, f := range info.Files { + if f.Pos() == token.NoPos { + // This can happen if the parser saw + // too many errors and bailed out. + // (Use parser.AllErrors to prevent that.) + continue + } + if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) { + continue + } + if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { + return info, path, exact + } + } + } + return nil, nil, false +} + +// InitialPackages returns a new slice containing the set of initial +// packages (Created + Imported) in unspecified order. +// +func (prog *Program) InitialPackages() []*PackageInfo { + infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported)) + infos = append(infos, prog.Created...) + for _, info := range prog.Imported { + infos = append(infos, info) + } + return infos +} + +// Package returns the ASTs and results of type checking for the +// specified package. +func (prog *Program) Package(path string) *PackageInfo { + if info, ok := prog.AllPackages[prog.importMap[path]]; ok { + return info + } + for _, info := range prog.Created { + if path == info.Pkg.Path() { + return info + } + } + return nil +} + +// ---------- Implementation ---------- + +// importer holds the working state of the algorithm. +type importer struct { + conf *Config // the client configuration + start time.Time // for logging + + progMu sync.Mutex // guards prog + prog *Program // the resulting program + + // findpkg is a memoization of FindPackage. + findpkgMu sync.Mutex // guards findpkg + findpkg map[findpkgKey]*findpkgValue + + importedMu sync.Mutex // guards imported + imported map[string]*importInfo // all imported packages (incl. failures) by import path + + // import dependency graph: graph[x][y] => x imports y + // + // Since non-importable packages cannot be cyclic, we ignore + // their imports, thus we only need the subgraph over importable + // packages. Nodes are identified by their import paths. + graphMu sync.Mutex + graph map[string]map[string]bool +} + +type findpkgKey struct { + importPath string + fromDir string + mode build.ImportMode +} + +type findpkgValue struct { + ready chan struct{} // closed to broadcast readiness + bp *build.Package + err error +} + +// importInfo tracks the success or failure of a single import. +// +// Upon completion, exactly one of info and err is non-nil: +// info on successful creation of a package, err otherwise. +// A successful package may still contain type errors. +// +type importInfo struct { + path string // import path + info *PackageInfo // results of typechecking (including errors) + complete chan struct{} // closed to broadcast that info is set. +} + +// awaitCompletion blocks until ii is complete, +// i.e. the info field is safe to inspect. +func (ii *importInfo) awaitCompletion() { + <-ii.complete // wait for close +} + +// Complete marks ii as complete. +// Its info and err fields will not be subsequently updated. +func (ii *importInfo) Complete(info *PackageInfo) { + if info == nil { + panic("info == nil") + } + ii.info = info + close(ii.complete) +} + +type importError struct { + path string // import path + err error // reason for failure to create a package +} + +// Load creates the initial packages specified by conf.{Create,Import}Pkgs, +// loading their dependencies packages as needed. +// +// On success, Load returns a Program containing a PackageInfo for +// each package. On failure, it returns an error. +// +// If AllowErrors is true, Load will return a Program even if some +// packages contained I/O, parser or type errors, or if dependencies +// were missing. (Such errors are accessible via PackageInfo.Errors. If +// false, Load will fail if any package had an error. +// +// It is an error if no packages were loaded. +// +func (conf *Config) Load() (*Program, error) { + // Create a simple default error handler for parse/type errors. + if conf.TypeChecker.Error == nil { + conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) } + } + + // Set default working directory for relative package references. + if conf.Cwd == "" { + var err error + conf.Cwd, err = os.Getwd() + if err != nil { + return nil, err + } + } + + // Install default FindPackage hook using go/build logic. + if conf.FindPackage == nil { + conf.FindPackage = (*build.Context).Import + } + + prog := &Program{ + Fset: conf.fset(), + Imported: make(map[string]*PackageInfo), + importMap: make(map[string]*types.Package), + AllPackages: make(map[*types.Package]*PackageInfo), + } + + imp := importer{ + conf: conf, + prog: prog, + findpkg: make(map[findpkgKey]*findpkgValue), + imported: make(map[string]*importInfo), + start: time.Now(), + graph: make(map[string]map[string]bool), + } + + // -- loading proper (concurrent phase) -------------------------------- + + var errpkgs []string // packages that contained errors + + // Load the initially imported packages and their dependencies, + // in parallel. + // No vendor check on packages imported from the command line. + infos, importErrors := imp.importAll("", conf.Cwd, conf.ImportPkgs, ignoreVendor) + for _, ie := range importErrors { + conf.TypeChecker.Error(ie.err) // failed to create package + errpkgs = append(errpkgs, ie.path) + } + for _, info := range infos { + prog.Imported[info.Pkg.Path()] = info + } + + // Augment the designated initial packages by their tests. + // Dependencies are loaded in parallel. + var xtestPkgs []*build.Package + for importPath, augment := range conf.ImportPkgs { + if !augment { + continue + } + + // No vendor check on packages imported from command line. + bp, err := imp.findPackage(importPath, conf.Cwd, ignoreVendor) + if err != nil { + // Package not found, or can't even parse package declaration. + // Already reported by previous loop; ignore it. + continue + } + + // Needs external test package? + if len(bp.XTestGoFiles) > 0 { + xtestPkgs = append(xtestPkgs, bp) + } + + // Consult the cache using the canonical package path. + path := bp.ImportPath + imp.importedMu.Lock() // (unnecessary, we're sequential here) + ii, ok := imp.imported[path] + // Paranoid checks added due to issue #11012. + if !ok { + // Unreachable. + // The previous loop called importAll and thus + // startLoad for each path in ImportPkgs, which + // populates imp.imported[path] with a non-zero value. + panic(fmt.Sprintf("imported[%q] not found", path)) + } + if ii == nil { + // Unreachable. + // The ii values in this loop are the same as in + // the previous loop, which enforced the invariant + // that at least one of ii.err and ii.info is non-nil. + panic(fmt.Sprintf("imported[%q] == nil", path)) + } + if ii.info == nil { + // Unreachable. + // awaitCompletion has the postcondition + // ii.info != nil. + panic(fmt.Sprintf("imported[%q].info = nil", path)) + } + info := ii.info + imp.importedMu.Unlock() + + // Parse the in-package test files. + files, errs := imp.conf.parsePackageFiles(bp, 't') + for _, err := range errs { + info.appendError(err) + } + + // The test files augmenting package P cannot be imported, + // but may import packages that import P, + // so we must disable the cycle check. + imp.addFiles(info, files, false) + } + + createPkg := func(path, dir string, files []*ast.File, errs []error) { + info := imp.newPackageInfo(path, dir) + for _, err := range errs { + info.appendError(err) + } + + // Ad hoc packages are non-importable, + // so no cycle check is needed. + // addFiles loads dependencies in parallel. + imp.addFiles(info, files, false) + prog.Created = append(prog.Created, info) + } + + // Create packages specified by conf.CreatePkgs. + for _, cp := range conf.CreatePkgs { + files, errs := parseFiles(conf.fset(), conf.build(), nil, conf.Cwd, cp.Filenames, conf.ParserMode) + files = append(files, cp.Files...) + + path := cp.Path + if path == "" { + if len(files) > 0 { + path = files[0].Name.Name + } else { + path = "(unnamed)" + } + } + + dir := conf.Cwd + if len(files) > 0 && files[0].Pos().IsValid() { + dir = filepath.Dir(conf.fset().File(files[0].Pos()).Name()) + } + createPkg(path, dir, files, errs) + } + + // Create external test packages. + sort.Sort(byImportPath(xtestPkgs)) + for _, bp := range xtestPkgs { + files, errs := imp.conf.parsePackageFiles(bp, 'x') + createPkg(bp.ImportPath+"_test", bp.Dir, files, errs) + } + + // -- finishing up (sequential) ---------------------------------------- + + if len(prog.Imported)+len(prog.Created) == 0 { + return nil, errors.New("no initial packages were loaded") + } + + // Create infos for indirectly imported packages. + // e.g. incomplete packages without syntax, loaded from export data. + for _, obj := range prog.importMap { + info := prog.AllPackages[obj] + if info == nil { + prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true} + } else { + // finished + info.checker = nil + info.errorFunc = nil + } + } + + if !conf.AllowErrors { + // Report errors in indirectly imported packages. + for _, info := range prog.AllPackages { + if len(info.Errors) > 0 { + errpkgs = append(errpkgs, info.Pkg.Path()) + } + } + if errpkgs != nil { + var more string + if len(errpkgs) > 3 { + more = fmt.Sprintf(" and %d more", len(errpkgs)-3) + errpkgs = errpkgs[:3] + } + return nil, fmt.Errorf("couldn't load packages due to errors: %s%s", + strings.Join(errpkgs, ", "), more) + } + } + + markErrorFreePackages(prog.AllPackages) + + return prog, nil +} + +type byImportPath []*build.Package + +func (b byImportPath) Len() int { return len(b) } +func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath } +func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] } + +// markErrorFreePackages sets the TransitivelyErrorFree flag on all +// applicable packages. +func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) { + // Build the transpose of the import graph. + importedBy := make(map[*types.Package]map[*types.Package]bool) + for P := range allPackages { + for _, Q := range P.Imports() { + clients, ok := importedBy[Q] + if !ok { + clients = make(map[*types.Package]bool) + importedBy[Q] = clients + } + clients[P] = true + } + } + + // Find all packages reachable from some error package. + reachable := make(map[*types.Package]bool) + var visit func(*types.Package) + visit = func(p *types.Package) { + if !reachable[p] { + reachable[p] = true + for q := range importedBy[p] { + visit(q) + } + } + } + for _, info := range allPackages { + if len(info.Errors) > 0 { + visit(info.Pkg) + } + } + + // Mark the others as "transitively error-free". + for _, info := range allPackages { + if !reachable[info.Pkg] { + info.TransitivelyErrorFree = true + } + } +} + +// build returns the effective build context. +func (conf *Config) build() *build.Context { + if conf.Build != nil { + return conf.Build + } + return &build.Default +} + +// parsePackageFiles enumerates the files belonging to package path, +// then loads, parses and returns them, plus a list of I/O or parse +// errors that were encountered. +// +// 'which' indicates which files to include: +// 'g': include non-test *.go source files (GoFiles + processed CgoFiles) +// 't': include in-package *_test.go source files (TestGoFiles) +// 'x': include external *_test.go source files. (XTestGoFiles) +// +func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) { + if bp.ImportPath == "unsafe" { + return nil, nil + } + var filenames []string + switch which { + case 'g': + filenames = bp.GoFiles + case 't': + filenames = bp.TestGoFiles + case 'x': + filenames = bp.XTestGoFiles + default: + panic(which) + } + + files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode) + + // Preprocess CgoFiles and parse the outputs (sequentially). + if which == 'g' && bp.CgoFiles != nil { + cgofiles, err := cgo.ProcessFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode) + if err != nil { + errs = append(errs, err) + } else { + files = append(files, cgofiles...) + } + } + + return files, errs +} + +// doImport imports the package denoted by path. +// It implements the types.Importer signature. +// +// It returns an error if a package could not be created +// (e.g. go/build or parse error), but type errors are reported via +// the types.Config.Error callback (the first of which is also saved +// in the package's PackageInfo). +// +// Idempotent. +// +func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) { + if to == "C" { + // This should be unreachable, but ad hoc packages are + // not currently subject to cgo preprocessing. + // See https://golang.org/issue/11627. + return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`, + from.Pkg.Path()) + } + + bp, err := imp.findPackage(to, from.dir, 0) + if err != nil { + return nil, err + } + + // The standard unsafe package is handled specially, + // and has no PackageInfo. + if bp.ImportPath == "unsafe" { + return types.Unsafe, nil + } + + // Look for the package in the cache using its canonical path. + path := bp.ImportPath + imp.importedMu.Lock() + ii := imp.imported[path] + imp.importedMu.Unlock() + if ii == nil { + panic("internal error: unexpected import: " + path) + } + if ii.info != nil { + return ii.info.Pkg, nil + } + + // Import of incomplete package: this indicates a cycle. + fromPath := from.Pkg.Path() + if cycle := imp.findPath(path, fromPath); cycle != nil { + // Normalize cycle: start from alphabetically largest node. + pos, start := -1, "" + for i, s := range cycle { + if pos < 0 || s > start { + pos, start = i, s + } + } + cycle = append(cycle, cycle[:pos]...)[pos:] // rotate cycle to start from largest + cycle = append(cycle, cycle[0]) // add start node to end to show cycliness + return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> ")) + } + + panic("internal error: import of incomplete (yet acyclic) package: " + fromPath) +} + +// findPackage locates the package denoted by the importPath in the +// specified directory. +func (imp *importer) findPackage(importPath, fromDir string, mode build.ImportMode) (*build.Package, error) { + // We use a non-blocking duplicate-suppressing cache (gopl.io §9.7) + // to avoid holding the lock around FindPackage. + key := findpkgKey{importPath, fromDir, mode} + imp.findpkgMu.Lock() + v, ok := imp.findpkg[key] + if ok { + // cache hit + imp.findpkgMu.Unlock() + + <-v.ready // wait for entry to become ready + } else { + // Cache miss: this goroutine becomes responsible for + // populating the map entry and broadcasting its readiness. + v = &findpkgValue{ready: make(chan struct{})} + imp.findpkg[key] = v + imp.findpkgMu.Unlock() + + ioLimit <- true + v.bp, v.err = imp.conf.FindPackage(imp.conf.build(), importPath, fromDir, mode) + <-ioLimit + + if _, ok := v.err.(*build.NoGoError); ok { + v.err = nil // empty directory is not an error + } + + close(v.ready) // broadcast ready condition + } + return v.bp, v.err +} + +// importAll loads, parses, and type-checks the specified packages in +// parallel and returns their completed importInfos in unspecified order. +// +// fromPath is the package path of the importing package, if it is +// importable, "" otherwise. It is used for cycle detection. +// +// fromDir is the directory containing the import declaration that +// caused these imports. +// +func (imp *importer) importAll(fromPath, fromDir string, imports map[string]bool, mode build.ImportMode) (infos []*PackageInfo, errors []importError) { + // TODO(adonovan): opt: do the loop in parallel once + // findPackage is non-blocking. + var pending []*importInfo + for importPath := range imports { + bp, err := imp.findPackage(importPath, fromDir, mode) + if err != nil { + errors = append(errors, importError{ + path: importPath, + err: err, + }) + continue + } + pending = append(pending, imp.startLoad(bp)) + } + + if fromPath != "" { + // We're loading a set of imports. + // + // We must record graph edges from the importing package + // to its dependencies, and check for cycles. + imp.graphMu.Lock() + deps, ok := imp.graph[fromPath] + if !ok { + deps = make(map[string]bool) + imp.graph[fromPath] = deps + } + for _, ii := range pending { + deps[ii.path] = true + } + imp.graphMu.Unlock() + } + + for _, ii := range pending { + if fromPath != "" { + if cycle := imp.findPath(ii.path, fromPath); cycle != nil { + // Cycle-forming import: we must not await its + // completion since it would deadlock. + // + // We don't record the error in ii since + // the error is really associated with the + // cycle-forming edge, not the package itself. + // (Also it would complicate the + // invariants of importPath completion.) + if trace { + fmt.Fprintf(os.Stderr, "import cycle: %q\n", cycle) + } + continue + } + } + ii.awaitCompletion() + infos = append(infos, ii.info) + } + + return infos, errors +} + +// findPath returns an arbitrary path from 'from' to 'to' in the import +// graph, or nil if there was none. +func (imp *importer) findPath(from, to string) []string { + imp.graphMu.Lock() + defer imp.graphMu.Unlock() + + seen := make(map[string]bool) + var search func(stack []string, importPath string) []string + search = func(stack []string, importPath string) []string { + if !seen[importPath] { + seen[importPath] = true + stack = append(stack, importPath) + if importPath == to { + return stack + } + for x := range imp.graph[importPath] { + if p := search(stack, x); p != nil { + return p + } + } + } + return nil + } + return search(make([]string, 0, 20), from) +} + +// startLoad initiates the loading, parsing and type-checking of the +// specified package and its dependencies, if it has not already begun. +// +// It returns an importInfo, not necessarily in a completed state. The +// caller must call awaitCompletion() before accessing its info field. +// +// startLoad is concurrency-safe and idempotent. +// +func (imp *importer) startLoad(bp *build.Package) *importInfo { + path := bp.ImportPath + imp.importedMu.Lock() + ii, ok := imp.imported[path] + if !ok { + ii = &importInfo{path: path, complete: make(chan struct{})} + imp.imported[path] = ii + go func() { + info := imp.load(bp) + ii.Complete(info) + }() + } + imp.importedMu.Unlock() + + return ii +} + +// load implements package loading by parsing Go source files +// located by go/build. +func (imp *importer) load(bp *build.Package) *PackageInfo { + info := imp.newPackageInfo(bp.ImportPath, bp.Dir) + info.Importable = true + files, errs := imp.conf.parsePackageFiles(bp, 'g') + for _, err := range errs { + info.appendError(err) + } + + imp.addFiles(info, files, true) + + imp.progMu.Lock() + imp.prog.importMap[bp.ImportPath] = info.Pkg + imp.progMu.Unlock() + + return info +} + +// addFiles adds and type-checks the specified files to info, loading +// their dependencies if needed. The order of files determines the +// package initialization order. It may be called multiple times on the +// same package. Errors are appended to the info.Errors field. +// +// cycleCheck determines whether the imports within files create +// dependency edges that should be checked for potential cycles. +// +func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) { + // Ensure the dependencies are loaded, in parallel. + var fromPath string + if cycleCheck { + fromPath = info.Pkg.Path() + } + // TODO(adonovan): opt: make the caller do scanImports. + // Callers with a build.Package can skip it. + imp.importAll(fromPath, info.dir, scanImports(files), 0) + + if trace { + fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n", + time.Since(imp.start), info.Pkg.Path(), len(files)) + } + + // Don't call checker.Files on Unsafe, even with zero files, + // because it would mutate the package, which is a global. + if info.Pkg == types.Unsafe { + if len(files) > 0 { + panic(`"unsafe" package contains unexpected files`) + } + } else { + // Ignore the returned (first) error since we + // already collect them all in the PackageInfo. + info.checker.Files(files) + info.Files = append(info.Files, files...) + } + + if imp.conf.AfterTypeCheck != nil { + imp.conf.AfterTypeCheck(info, files) + } + + if trace { + fmt.Fprintf(os.Stderr, "%s: stop %q\n", + time.Since(imp.start), info.Pkg.Path()) + } +} + +func (imp *importer) newPackageInfo(path, dir string) *PackageInfo { + var pkg *types.Package + if path == "unsafe" { + pkg = types.Unsafe + } else { + pkg = types.NewPackage(path, "") + } + info := &PackageInfo{ + Pkg: pkg, + Info: types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + }, + errorFunc: imp.conf.TypeChecker.Error, + dir: dir, + } + + // Copy the types.Config so we can vary it across PackageInfos. + tc := imp.conf.TypeChecker + tc.IgnoreFuncBodies = false + if f := imp.conf.TypeCheckFuncBodies; f != nil { + tc.IgnoreFuncBodies = !f(path) + } + tc.Importer = closure{imp, info} + tc.Error = info.appendError // appendError wraps the user's Error function + + info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) + imp.progMu.Lock() + imp.prog.AllPackages[pkg] = info + imp.progMu.Unlock() + return info +} + +type closure struct { + imp *importer + info *PackageInfo +} + +func (c closure) Import(to string) (*types.Package, error) { return c.imp.doImport(c.info, to) } diff --git a/vendor/golang.org/x/tools/go/loader/util.go b/vendor/golang.org/x/tools/go/loader/util.go new file mode 100644 index 0000000000..7f38dd7407 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/util.go @@ -0,0 +1,124 @@ +// Copyright 2013 The Go 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 loader + +import ( + "go/ast" + "go/build" + "go/parser" + "go/token" + "io" + "os" + "strconv" + "sync" + + "golang.org/x/tools/go/buildutil" +) + +// We use a counting semaphore to limit +// the number of parallel I/O calls per process. +var ioLimit = make(chan bool, 10) + +// parseFiles parses the Go source files within directory dir and +// returns the ASTs of the ones that could be at least partially parsed, +// along with a list of I/O and parse errors encountered. +// +// I/O is done via ctxt, which may specify a virtual file system. +// displayPath is used to transform the filenames attached to the ASTs. +// +func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { + if displayPath == nil { + displayPath = func(path string) string { return path } + } + var wg sync.WaitGroup + n := len(files) + parsed := make([]*ast.File, n) + errors := make([]error, n) + for i, file := range files { + if !buildutil.IsAbsPath(ctxt, file) { + file = buildutil.JoinPath(ctxt, dir, file) + } + wg.Add(1) + go func(i int, file string) { + ioLimit <- true // wait + defer func() { + wg.Done() + <-ioLimit // signal + }() + var rd io.ReadCloser + var err error + if ctxt.OpenFile != nil { + rd, err = ctxt.OpenFile(file) + } else { + rd, err = os.Open(file) + } + if err != nil { + errors[i] = err // open failed + return + } + + // ParseFile may return both an AST and an error. + parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) + rd.Close() + }(i, file) + } + wg.Wait() + + // Eliminate nils, preserving order. + var o int + for _, f := range parsed { + if f != nil { + parsed[o] = f + o++ + } + } + parsed = parsed[:o] + + o = 0 + for _, err := range errors { + if err != nil { + errors[o] = err + o++ + } + } + errors = errors[:o] + + return parsed, errors +} + +// scanImports returns the set of all import paths from all +// import specs in the specified files. +func scanImports(files []*ast.File) map[string]bool { + imports := make(map[string]bool) + for _, f := range files { + for _, decl := range f.Decls { + if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { + for _, spec := range decl.Specs { + spec := spec.(*ast.ImportSpec) + + // NB: do not assume the program is well-formed! + path, err := strconv.Unquote(spec.Path.Value) + if err != nil { + continue // quietly ignore the error + } + if path == "C" { + continue // skip pseudopackage + } + imports[path] = true + } + } + } + } + return imports +} + +// ---------- Internal helpers ---------- + +// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) +func tokenFileContainsPos(f *token.File, pos token.Pos) bool { + p := int(pos) + base := f.Base() + return base <= p && p < base+f.Size() +} diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 2ac7c02a3b..a7bd92966f 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -6,26 +6,26 @@ package packages import ( "bytes" + "context" "encoding/json" "fmt" "go/types" - "io/ioutil" "log" "os" "os/exec" "path" "path/filepath" "reflect" - "regexp" + "sort" "strconv" "strings" "sync" - "time" "unicode" "golang.org/x/tools/go/internal/packagesdriver" - "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/semver" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/packagesinternal" + "golang.org/x/xerrors" ) // debug controls verbose logging. @@ -44,16 +44,21 @@ type responseDeduper struct { dr *driverResponse } -// init fills in r with a driverResponse. -func (r *responseDeduper) init(dr *driverResponse) { - r.dr = dr - r.seenRoots = map[string]bool{} - r.seenPackages = map[string]*Package{} +func newDeduper() *responseDeduper { + return &responseDeduper{ + dr: &driverResponse{}, + seenRoots: map[string]bool{}, + seenPackages: map[string]*Package{}, + } +} + +// addAll fills in r with a driverResponse. +func (r *responseDeduper) addAll(dr *driverResponse) { for _, pkg := range dr.Packages { - r.seenPackages[pkg.ID] = pkg + r.addPackage(pkg) } for _, root := range dr.Roots { - r.seenRoots[root] = true + r.addRoot(root) } } @@ -73,25 +78,47 @@ func (r *responseDeduper) addRoot(id string) { r.dr.Roots = append(r.dr.Roots, id) } -// goInfo contains global information from the go tool. -type goInfo struct { - rootDirs map[string]string - env goEnv +type golistState struct { + cfg *Config + ctx context.Context + + envOnce sync.Once + goEnvError error + goEnv map[string]string + + rootsOnce sync.Once + rootDirsError error + rootDirs map[string]string + + // vendorDirs caches the (non)existence of vendor directories. + vendorDirs map[string]bool } -type goEnv struct { - modulesOn bool +// getEnv returns Go environment variables. Only specific variables are +// populated -- computing all of them is slow. +func (state *golistState) getEnv() (map[string]string, error) { + state.envOnce.Do(func() { + var b *bytes.Buffer + b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH") + if state.goEnvError != nil { + return + } + + state.goEnv = make(map[string]string) + decoder := json.NewDecoder(b) + if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil { + return + } + }) + return state.goEnv, state.goEnvError } -func determineEnv(cfg *Config) goEnv { - buf, err := invokeGo(cfg, "env", "GOMOD") +// mustGetEnv is a convenience function that can be used if getEnv has already succeeded. +func (state *golistState) mustGetEnv() map[string]string { + env, err := state.getEnv() if err != nil { - return goEnv{} + panic(fmt.Sprintf("mustGetEnv: %v", err)) } - gomod := bytes.TrimSpace(buf.Bytes()) - - env := goEnv{} - env.modulesOn = len(gomod) > 0 return env } @@ -99,47 +126,38 @@ func determineEnv(cfg *Config) goEnv { // the build system package structure. // See driver for more details. func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { - var sizes types.Sizes + // Make sure that any asynchronous go commands are killed when we return. + parentCtx := cfg.Context + if parentCtx == nil { + parentCtx = context.Background() + } + ctx, cancel := context.WithCancel(parentCtx) + defer cancel() + + response := newDeduper() + + // Fill in response.Sizes asynchronously if necessary. var sizeserr error var sizeswg sync.WaitGroup if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { sizeswg.Add(1) go func() { - sizes, sizeserr = getSizes(cfg) + var sizes types.Sizes + sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.gocmdRunner, cfg.Dir) + // types.SizesFor always returns nil or a *types.StdSizes. + response.dr.Sizes, _ = sizes.(*types.StdSizes) sizeswg.Done() }() } - defer sizeswg.Wait() - // start fetching rootDirs - var info goInfo - var rootDirsReady, envReady = make(chan struct{}), make(chan struct{}) - go func() { - info.rootDirs = determineRootDirs(cfg) - close(rootDirsReady) - }() - go func() { - info.env = determineEnv(cfg) - close(envReady) - }() - getGoInfo := func() *goInfo { - <-rootDirsReady - <-envReady - return &info - } - - // Ensure that we don't leak goroutines: Load is synchronous, so callers will - // not expect it to access the fields of cfg after the call returns. - defer getGoInfo() - - // always pass getGoInfo to golistDriver - golistDriver := func(cfg *Config, patterns ...string) (*driverResponse, error) { - return golistDriver(cfg, getGoInfo, patterns...) + state := &golistState{ + cfg: cfg, + ctx: ctx, + vendorDirs: map[string]bool{}, } // Determine files requested in contains patterns var containFiles []string - var packagesNamed []string restPatterns := make([]string, 0, len(patterns)) // Extract file= and other [querytype]= patterns. Report an error if querytype // doesn't exist. @@ -155,8 +173,6 @@ extractQueries: containFiles = append(containFiles, value) case "pattern": restPatterns = append(restPatterns, value) - case "iamashamedtousethedisabledqueryname": - packagesNamed = append(packagesNamed, value) case "": // not a reserved query restPatterns = append(restPatterns, pattern) default: @@ -172,52 +188,34 @@ extractQueries: } } - response := &responseDeduper{} - var err error - // See if we have any patterns to pass through to go list. Zero initial // patterns also requires a go list call, since it's the equivalent of // ".". if len(restPatterns) > 0 || len(patterns) == 0 { - dr, err := golistDriver(cfg, restPatterns...) + dr, err := state.createDriverResponse(restPatterns...) if err != nil { return nil, err } - response.init(dr) - } else { - response.init(&driverResponse{}) + response.addAll(dr) } - sizeswg.Wait() - if sizeserr != nil { - return nil, sizeserr - } - // types.SizesFor always returns nil or a *types.StdSizes - response.dr.Sizes, _ = sizes.(*types.StdSizes) - - var containsCandidates []string - if len(containFiles) != 0 { - if err := runContainsQueries(cfg, golistDriver, response, containFiles, getGoInfo); err != nil { + if err := state.runContainsQueries(response, containFiles); err != nil { return nil, err } } - if len(packagesNamed) != 0 { - if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil { - return nil, err - } - } - - modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo) + modifiedPkgs, needPkgs, err := state.processGolistOverlay(response) if err != nil { return nil, err } + + var containsCandidates []string if len(containFiles) > 0 { containsCandidates = append(containsCandidates, modifiedPkgs...) containsCandidates = append(containsCandidates, needPkgs...) } - if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs, getGoInfo); err != nil { + if err := state.addNeededOverlayPackages(response, needPkgs); err != nil { return nil, err } // Check candidate packages for containFiles. @@ -246,28 +244,32 @@ extractQueries: } } + sizeswg.Wait() + if sizeserr != nil { + return nil, sizeserr + } return response.dr, nil } -func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string, getGoInfo func() *goInfo) error { +func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error { if len(pkgs) == 0 { return nil } - dr, err := driver(cfg, pkgs...) + dr, err := state.createDriverResponse(pkgs...) if err != nil { return err } for _, pkg := range dr.Packages { response.addPackage(pkg) } - _, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo) + _, needPkgs, err := state.processGolistOverlay(response) if err != nil { return err } - return addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo) + return state.addNeededOverlayPackages(response, needPkgs) } -func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string, goInfo func() *goInfo) error { +func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error { for _, query := range queries { // TODO(matloob): Do only one query per directory. fdir := filepath.Dir(query) @@ -277,44 +279,17 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q if err != nil { return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) } - dirResponse, err := driver(cfg, pattern) - if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) { - // There was an error loading the package. Try to load the file as an ad-hoc package. - // Usually the error will appear in a returned package, but may not if we're in modules mode - // and the ad-hoc is located outside a module. + dirResponse, err := state.createDriverResponse(pattern) + + // If there was an error loading the package, or the package is returned + // with errors, try to load the file as an ad-hoc package. + // Usually the error will appear in a returned package, but may not if we're + // in module mode and the ad-hoc is located outside a module. + if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && + len(dirResponse.Packages[0].Errors) == 1 { var queryErr error - dirResponse, queryErr = driver(cfg, query) - if queryErr != nil { - // Return the original error if the attempt to fall back failed. - return err - } - // If we get nothing back from `go list`, try to make this file into its own ad-hoc package. - if len(dirResponse.Packages) == 0 && queryErr == nil { - dirResponse.Packages = append(dirResponse.Packages, &Package{ - ID: "command-line-arguments", - PkgPath: query, - GoFiles: []string{query}, - CompiledGoFiles: []string{query}, - Imports: make(map[string]*Package), - }) - dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments") - } - // Special case to handle issue #33482: - // If this is a file= query for ad-hoc packages where the file only exists on an overlay, - // and exists outside of a module, add the file in for the package. - if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || - filepath.ToSlash(dirResponse.Packages[0].PkgPath) == filepath.ToSlash(query)) { - if len(dirResponse.Packages[0].GoFiles) == 0 { - filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath - // TODO(matloob): check if the file is outside of a root dir? - for path := range cfg.Overlay { - if path == filename { - dirResponse.Packages[0].Errors = nil - dirResponse.Packages[0].GoFiles = []string{path} - dirResponse.Packages[0].CompiledGoFiles = []string{path} - } - } - } + if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil { + return err // return the original error } } isRoot := make(map[string]bool, len(dirResponse.Roots)) @@ -342,276 +317,47 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q return nil } -// modCacheRegexp splits a path in a module cache into module, module version, and package. -var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) - -func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { - // calling `go env` isn't free; bail out if there's nothing to do. - if len(queries) == 0 { - return nil - } - // Determine which directories are relevant to scan. - roots, modRoot, err := roots(cfg) +// adhocPackage attempts to load or construct an ad-hoc package for a given +// query, if the original call to the driver produced inadequate results. +func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) { + response, err := state.createDriverResponse(query) if err != nil { - return err + return nil, err } - - // Scan the selected directories. Simple matches, from GOPATH/GOROOT - // or the local module, can simply be "go list"ed. Matches from the - // module cache need special treatment. - var matchesMu sync.Mutex - var simpleMatches, modCacheMatches []string - add := func(root gopathwalk.Root, dir string) { - // Walk calls this concurrently; protect the result slices. - matchesMu.Lock() - defer matchesMu.Unlock() - - path := dir - if dir != root.Path { - path = dir[len(root.Path)+1:] - } - if pathMatchesQueries(path, queries) { - switch root.Type { - case gopathwalk.RootModuleCache: - modCacheMatches = append(modCacheMatches, path) - case gopathwalk.RootCurrentModule: - // We'd need to read go.mod to find the full - // import path. Relative's easier. - rel, err := filepath.Rel(cfg.Dir, dir) - if err != nil { - // This ought to be impossible, since - // we found dir in the current module. - panic(err) - } - simpleMatches = append(simpleMatches, "./"+rel) - case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT: - simpleMatches = append(simpleMatches, path) - } - } - } - - startWalk := time.Now() - gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug}) - cfg.Logf("%v for walk", time.Since(startWalk)) - - // Weird special case: the top-level package in a module will be in - // whatever directory the user checked the repository out into. It's - // more reasonable for that to not match the package name. So, if there - // are any Go files in the mod root, query it just to be safe. - if modRoot != "" { - rel, err := filepath.Rel(cfg.Dir, modRoot) - if err != nil { - panic(err) // See above. - } - - files, err := ioutil.ReadDir(modRoot) - if err != nil { - panic(err) // See above. - } - - for _, f := range files { - if strings.HasSuffix(f.Name(), ".go") { - simpleMatches = append(simpleMatches, rel) - break - } - } - } - - addResponse := func(r *driverResponse) { - for _, pkg := range r.Packages { - response.addPackage(pkg) - for _, name := range queries { - if pkg.Name == name { - response.addRoot(pkg.ID) - break - } - } - } - } - - if len(simpleMatches) != 0 { - resp, err := driver(cfg, simpleMatches...) - if err != nil { - return err - } - addResponse(resp) - } - - // Module cache matches are tricky. We want to avoid downloading new - // versions of things, so we need to use the ones present in the cache. - // go list doesn't accept version specifiers, so we have to write out a - // temporary module, and do the list in that module. - if len(modCacheMatches) != 0 { - // Collect all the matches, deduplicating by major version - // and preferring the newest. - type modInfo struct { - mod string - major string - } - mods := make(map[modInfo]string) - var imports []string - for _, modPath := range modCacheMatches { - matches := modCacheRegexp.FindStringSubmatch(modPath) - mod, ver := filepath.ToSlash(matches[1]), matches[2] - importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3])) - - major := semver.Major(ver) - if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 { - mods[modInfo{mod, major}] = ver - } - - imports = append(imports, importPath) - } - - // Build the temporary module. - var gomod bytes.Buffer - gomod.WriteString("module modquery\nrequire (\n") - for mod, version := range mods { - gomod.WriteString("\t" + mod.mod + " " + version + "\n") - } - gomod.WriteString(")\n") - - tmpCfg := *cfg - - // We're only trying to look at stuff in the module cache, so - // disable the network. This should speed things up, and has - // prevented errors in at least one case, #28518. - tmpCfg.Env = append([]string{"GOPROXY=off"}, cfg.Env...) - - var err error - tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") - if err != nil { - return err - } - defer os.RemoveAll(tmpCfg.Dir) - - if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil { - return fmt.Errorf("writing go.mod for module cache query: %v", err) - } - - // Run the query, using the import paths calculated from the matches above. - resp, err := driver(&tmpCfg, imports...) - if err != nil { - return fmt.Errorf("querying module cache matches: %v", err) - } - addResponse(resp) - } - - return nil -} - -func getSizes(cfg *Config) (types.Sizes, error) { - return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) -} - -// roots selects the appropriate paths to walk based on the passed-in configuration, -// particularly the environment and the presence of a go.mod in cfg.Dir's parents. -func roots(cfg *Config) ([]gopathwalk.Root, string, error) { - stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD") - if err != nil { - return nil, "", err - } - - fields := strings.Split(stdout.String(), "\n") - if len(fields) != 4 || len(fields[3]) != 0 { - return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String()) - } - goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2] - var modDir string - if gomod != "" { - modDir = filepath.Dir(gomod) - } - - var roots []gopathwalk.Root - // Always add GOROOT. - roots = append(roots, gopathwalk.Root{ - Path: filepath.Join(goroot, "/src"), - Type: gopathwalk.RootGOROOT, - }) - // If modules are enabled, scan the module dir. - if modDir != "" { - roots = append(roots, gopathwalk.Root{ - Path: modDir, - Type: gopathwalk.RootCurrentModule, + // If we get nothing back from `go list`, + // try to make this file into its own ad-hoc package. + // TODO(rstambler): Should this check against the original response? + if len(response.Packages) == 0 { + response.Packages = append(response.Packages, &Package{ + ID: "command-line-arguments", + PkgPath: query, + GoFiles: []string{query}, + CompiledGoFiles: []string{query}, + Imports: make(map[string]*Package), }) + response.Roots = append(response.Roots, "command-line-arguments") } - // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode. - for _, p := range gopath { - if modDir != "" { - roots = append(roots, gopathwalk.Root{ - Path: filepath.Join(p, "/pkg/mod"), - Type: gopathwalk.RootModuleCache, - }) - } else { - roots = append(roots, gopathwalk.Root{ - Path: filepath.Join(p, "/src"), - Type: gopathwalk.RootGOPATH, - }) - } - } - - return roots, modDir, nil -} - -// These functions were copied from goimports. See further documentation there. - -// pathMatchesQueries is adapted from pkgIsCandidate. -// TODO: is it reasonable to do Contains here, rather than an exact match on a path component? -func pathMatchesQueries(path string, queries []string) bool { - lastTwo := lastTwoComponents(path) - for _, query := range queries { - if strings.Contains(lastTwo, query) { - return true - } - if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) { - lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) - if strings.Contains(lastTwo, query) { - return true + // Handle special cases. + if len(response.Packages) == 1 { + // golang/go#33482: If this is a file= query for ad-hoc packages where + // the file only exists on an overlay, and exists outside of a module, + // add the file to the package and remove the errors. + if response.Packages[0].ID == "command-line-arguments" || + filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) { + if len(response.Packages[0].GoFiles) == 0 { + filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath + // TODO(matloob): check if the file is outside of a root dir? + for path := range state.cfg.Overlay { + if path == filename { + response.Packages[0].Errors = nil + response.Packages[0].GoFiles = []string{path} + response.Packages[0].CompiledGoFiles = []string{path} + } + } } } } - return false -} - -// lastTwoComponents returns at most the last two path components -// of v, using either / or \ as the path separator. -func lastTwoComponents(v string) string { - nslash := 0 - for i := len(v) - 1; i >= 0; i-- { - if v[i] == '/' || v[i] == '\\' { - nslash++ - if nslash == 2 { - return v[i:] - } - } - } - return v -} - -func hasHyphenOrUpperASCII(s string) bool { - for i := 0; i < len(s); i++ { - b := s[i] - if b == '-' || ('A' <= b && b <= 'Z') { - return true - } - } - return false -} - -func lowerASCIIAndRemoveHyphen(s string) (ret string) { - buf := make([]byte, 0, len(s)) - for i := 0; i < len(s); i++ { - b := s[i] - switch { - case b == '-': - continue - case 'A' <= b && b <= 'Z': - buf = append(buf, b+('a'-'A')) - default: - buf = append(buf, b) - } - } - return string(buf) + return response, nil } // Fields must match go list; @@ -636,6 +382,7 @@ type jsonPackage struct { Imports []string ImportMap map[string]string Deps []string + Module *packagesinternal.Module TestGoFiles []string TestImports []string XTestGoFiles []string @@ -656,10 +403,9 @@ func otherFiles(p *jsonPackage) [][]string { return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} } -// golistDriver uses the "go list" command to expand the pattern -// words and return metadata for the specified packages. dir may be -// "" and env may be nil, as per os/exec.Command. -func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driverResponse, error) { +// createDriverResponse uses the "go list" command to expand the pattern +// words and return a response for the specified packages. +func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { // go list uses the following identifiers in ImportPath and Imports: // // "p" -- importable package or main (command) @@ -673,11 +419,13 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv // Run "go list" for complete // information on the specified packages. - buf, err := invokeGo(cfg, golistargs(cfg, words)...) + buf, err := state.invokeGo("list", golistargs(state.cfg, words)...) if err != nil { return nil, err } seen := make(map[string]*jsonPackage) + pkgs := make(map[string]*Package) + additionalErrors := make(map[string][]Error) // Decode the JSON and convert it to Package form. var response driverResponse for dec := json.NewDecoder(buf); dec.More(); { @@ -708,18 +456,81 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv // contained in a known module or GOPATH entry. This will allow the package to be // properly "reclaimed" when overlays are processed. if filepath.IsAbs(p.ImportPath) && p.Error != nil { - pkgPath, ok := getPkgPath(cfg, p.ImportPath, rootsDirs) + pkgPath, ok, err := state.getPkgPath(p.ImportPath) + if err != nil { + return nil, err + } if ok { p.ImportPath = pkgPath } } if old, found := seen[p.ImportPath]; found { - if !reflect.DeepEqual(p, old) { - return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) + // If one version of the package has an error, and the other doesn't, assume + // that this is a case where go list is reporting a fake dependency variant + // of the imported package: When a package tries to invalidly import another + // package, go list emits a variant of the imported package (with the same + // import path, but with an error on it, and the package will have a + // DepError set on it). An example of when this can happen is for imports of + // main packages: main packages can not be imported, but they may be + // separately matched and listed by another pattern. + // See golang.org/issue/36188 for more details. + + // The plan is that eventually, hopefully in Go 1.15, the error will be + // reported on the importing package rather than the duplicate "fake" + // version of the imported package. Once all supported versions of Go + // have the new behavior this logic can be deleted. + // TODO(matloob): delete the workaround logic once all supported versions of + // Go return the errors on the proper package. + + // There should be exactly one version of a package that doesn't have an + // error. + if old.Error == nil && p.Error == nil { + if !reflect.DeepEqual(p, old) { + return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) + } + continue } - // skip the duplicate - continue + + // Determine if this package's error needs to be bubbled up. + // This is a hack, and we expect for go list to eventually set the error + // on the package. + if old.Error != nil { + var errkind string + if strings.Contains(old.Error.Err, "not an importable package") { + errkind = "not an importable package" + } else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") { + errkind = "use of internal package not allowed" + } + if errkind != "" { + if len(old.Error.ImportStack) < 1 { + return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind) + } + importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1] + if importingPkg == old.ImportPath { + // Using an older version of Go which put this package itself on top of import + // stack, instead of the importer. Look for importer in second from top + // position. + if len(old.Error.ImportStack) < 2 { + return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind) + } + importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2] + } + additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ + Pos: old.Error.Pos, + Msg: old.Error.Err, + Kind: ListError, + }) + } + } + + // Make sure that if there's a version of the package without an error, + // that's the one reported to the user. + if old.Error == nil { + continue + } + + // This package will replace the old one at the end of the loop. } seen[p.ImportPath] = p @@ -729,6 +540,27 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), OtherFiles: absJoin(p.Dir, otherFiles(p)...), + forTest: p.ForTest, + module: p.Module, + } + + if (state.cfg.Mode&TypecheckCgo) != 0 && len(p.CgoFiles) != 0 { + if len(p.CompiledGoFiles) > len(p.GoFiles) { + // We need the cgo definitions, which are in the first + // CompiledGoFile after the non-cgo ones. This is a hack but there + // isn't currently a better way to find it. We also need the pure + // Go files and unprocessed cgo files, all of which are already + // in pkg.GoFiles. + cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] + pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) + } else { + // golang/go#38990: go list silently fails to do cgo processing + pkg.CompiledGoFiles = nil + pkg.Errors = append(pkg.Errors, Error{ + Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?", + Kind: ListError, + }) + } } // Work around https://golang.org/issue/28749: @@ -817,29 +649,37 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv }) } + pkgs[pkg.ID] = pkg + } + + for id, errs := range additionalErrors { + if p, ok := pkgs[id]; ok { + p.Errors = append(p.Errors, errs...) + } + } + for _, pkg := range pkgs { response.Packages = append(response.Packages, pkg) } + sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID }) return &response, nil } // getPkgPath finds the package path of a directory if it's relative to a root directory. -func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) { +func (state *golistState) getPkgPath(dir string) (string, bool, error) { absDir, err := filepath.Abs(dir) if err != nil { - cfg.Logf("error getting absolute path of %s: %v", dir, err) - return "", false + return "", false, err } - for rdir, rpath := range goInfo().rootDirs { - absRdir, err := filepath.Abs(rdir) - if err != nil { - cfg.Logf("error getting absolute path of %s: %v", rdir, err) - continue - } + roots, err := state.determineRootDirs() + if err != nil { + return "", false, err + } + + for rdir, rpath := range roots { // Make sure that the directory is in the module, // to avoid creating a path relative to another module. - if !strings.HasPrefix(absDir, absRdir) { - cfg.Logf("%s does not have prefix %s", absDir, absRdir) + if !strings.HasPrefix(absDir, rdir) { continue } // TODO(matloob): This doesn't properly handle symlinks. @@ -854,11 +694,11 @@ func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) { // Once the file is saved, gopls, or the next invocation of the tool will get the correct // result straight from golist. // TODO(matloob): Implement module tiebreaking? - return path.Join(rpath, filepath.ToSlash(r)), true + return path.Join(rpath, filepath.ToSlash(r)), true, nil } - return filepath.ToSlash(r), true + return filepath.ToSlash(r), true, nil } - return "", false + return "", false, nil } // absJoin absolutizes and flattens the lists of files. @@ -877,8 +717,8 @@ func absJoin(dir string, fileses ...[]string) (res []string) { func golistargs(cfg *Config, words []string) []string { const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo fullargs := []string{ - "list", "-e", "-json", - fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), + "-e", "-json", + fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0), fmt.Sprintf("-test=%t", cfg.Tests), fmt.Sprintf("-export=%t", usesExportData(cfg)), fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), @@ -893,25 +733,23 @@ func golistargs(cfg *Config, words []string) []string { } // invokeGo returns the stdout of a go command invocation. -func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd := exec.CommandContext(cfg.Context, "go", args...) - // On darwin the cwd gets resolved to the real path, which breaks anything that - // expects the working directory to keep the original path, including the - // go command when dealing with modules. - // The Go stdlib has a special feature where if the cwd and the PWD are the - // same node then it trusts the PWD, so by setting it in the env for the child - // process we fix up all the paths returned by the go command. - cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) - cmd.Dir = cfg.Dir - cmd.Stdout = stdout - cmd.Stderr = stderr - defer func(start time.Time) { - cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr, stdout) - }(time.Now()) +func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { + cfg := state.cfg - if err := cmd.Run(); err != nil { + inv := gocommand.Invocation{ + Verb: verb, + Args: args, + BuildFlags: cfg.BuildFlags, + Env: cfg.Env, + Logf: cfg.Logf, + WorkingDir: cfg.Dir, + } + gocmdRunner := cfg.gocmdRunner + if gocmdRunner == nil { + gocmdRunner = &gocommand.Runner{} + } + stdout, stderr, _, err := gocmdRunner.RunRaw(cfg.Context, inv) + if err != nil { // Check for 'go' executable not being found. if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) @@ -921,7 +759,7 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { if !ok { // Catastrophic error: // - context cancellation - return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) + return nil, xerrors.Errorf("couldn't run 'go': %w", err) } // Old go version? @@ -948,7 +786,12 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) } if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { - if strings.HasPrefix(strings.TrimLeftFunc(stderr.String()[len("# "):], isPkgPathRune), "\n") { + msg := stderr.String()[len("# "):] + if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") { + return stdout, nil + } + // Treat pkg-config errors as a special case (golang.org/issue/36770). + if strings.HasPrefix(msg, "pkg-config") { return stdout, nil } } @@ -1037,16 +880,6 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) } } - - // As of writing, go list -export prints some non-fatal compilation - // errors to stderr, even with -e set. We would prefer that it put - // them in the Package.Error JSON (see https://golang.org/issue/26319). - // In the meantime, there's nowhere good to put them, but they can - // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS - // is set. - if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { - fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) - } return stdout, nil } diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go index a7de62299d..3c99b6e48d 100644 --- a/vendor/golang.org/x/tools/go/packages/golist_overlay.go +++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go @@ -1,12 +1,14 @@ package packages import ( - "bytes" "encoding/json" "fmt" "go/parser" "go/token" + "log" + "os" "path/filepath" + "sort" "strconv" "strings" ) @@ -16,15 +18,20 @@ import ( // sometimes incorrect. // TODO(matloob): Handle unsupported cases, including the following: // - determining the correct package to add given a new import path -func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func() *goInfo) (modifiedPkgs, needPkgs []string, err error) { +func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) { havePkgs := make(map[string]string) // importPath -> non-test package ID needPkgsSet := make(map[string]bool) modifiedPkgsSet := make(map[string]bool) + pkgOfDir := make(map[string][]*Package) for _, pkg := range response.dr.Packages { // This is an approximation of import path to id. This can be // wrong for tests, vendored packages, and a number of other cases. havePkgs[pkg.PkgPath] = pkg.ID + x := commonDir(pkg.GoFiles) + if x != "" { + pkgOfDir[x] = append(pkgOfDir[x], pkg) + } } // If no new imports are added, it is safe to avoid loading any needPkgs. @@ -34,7 +41,23 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( // potentially modifying the transitive set of dependencies). var overlayAddsImports bool - for opath, contents := range cfg.Overlay { + // If both a package and its test package are created by the overlay, we + // need the real package first. Process all non-test files before test + // files, and make the whole process deterministic while we're at it. + var overlayFiles []string + for opath := range state.cfg.Overlay { + overlayFiles = append(overlayFiles, opath) + } + sort.Slice(overlayFiles, func(i, j int) bool { + iTest := strings.HasSuffix(overlayFiles[i], "_test.go") + jTest := strings.HasSuffix(overlayFiles[j], "_test.go") + if iTest != jTest { + return !iTest // non-tests are before tests. + } + return overlayFiles[i] < overlayFiles[j] + }) + for _, opath := range overlayFiles { + contents := state.cfg.Overlay[opath] base := filepath.Base(opath) dir := filepath.Dir(opath) var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant @@ -47,6 +70,9 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( // to the overlay. continue } + // if all the overlay files belong to a different package, change the package + // name to that package. Otherwise leave it alone; there will be an error message. + maybeFixPackageName(pkgName, pkgOfDir, dir) nextPackage: for _, p := range response.dr.Packages { if pkgName != p.Name && p.ID != "command-line-arguments" { @@ -64,14 +90,8 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( testVariantOf = p continue nextPackage } + // We must have already seen the package of which this is a test variant. if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { - // If we've already seen the test variant, - // make sure to label which package it is a test variant of. - if hasTestFiles(pkg) { - testVariantOf = p - continue nextPackage - } - // If we have already seen the package of which this is a test variant. if hasTestFiles(p) { testVariantOf = pkg } @@ -86,7 +106,10 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( if pkg == nil { // Try to find the module or gopath dir the file is contained in. // Then for modules, add the module opath to the beginning. - pkgPath, ok := getPkgPath(cfg, dir, rootDirs) + pkgPath, ok, err := state.getPkgPath(dir) + if err != nil { + return nil, nil, err + } if !ok { break } @@ -114,6 +137,11 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( if isTestFile && !isXTest && testVariantOf != nil { pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) + // Add the package under test and its imports to the test variant. + pkg.forTest = testVariantOf.PkgPath + for k, v := range testVariantOf.Imports { + pkg.Imports[k] = &Package{ID: v.ID} + } } } } @@ -130,42 +158,45 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( continue } for _, imp := range imports { - _, found := pkg.Imports[imp] - if !found { - overlayAddsImports = true - // TODO(matloob): Handle cases when the following block isn't correct. - // These include imports of vendored packages, etc. - id, ok := havePkgs[imp] - if !ok { - id = imp - } - pkg.Imports[imp] = &Package{ID: id} - // Add dependencies to the non-test variant version of this package as wel. - if testVariantOf != nil { - testVariantOf.Imports[imp] = &Package{ID: id} + if _, found := pkg.Imports[imp]; found { + continue + } + overlayAddsImports = true + id, ok := havePkgs[imp] + if !ok { + var err error + id, err = state.resolveImport(dir, imp) + if err != nil { + return nil, nil, err } } + pkg.Imports[imp] = &Package{ID: id} + // Add dependencies to the non-test variant version of this package as well. + if testVariantOf != nil { + testVariantOf.Imports[imp] = &Package{ID: id} + } } - continue } - // toPkgPath tries to guess the package path given the id. - // This isn't always correct -- it's certainly wrong for - // vendored packages' paths. - toPkgPath := func(id string) string { - // TODO(matloob): Handle vendor paths. - i := strings.IndexByte(id, ' ') - if i >= 0 { - return id[:i] + // toPkgPath guesses the package path given the id. + toPkgPath := func(sourceDir, id string) (string, error) { + if i := strings.IndexByte(id, ' '); i >= 0 { + return state.resolveImport(sourceDir, id[:i]) } - return id + return state.resolveImport(sourceDir, id) } - // Do another pass now that new packages have been created to determine the - // set of missing packages. + // Now that new packages have been created, do another pass to determine + // the new set of missing packages. for _, pkg := range response.dr.Packages { for _, imp := range pkg.Imports { - pkgPath := toPkgPath(imp.ID) + if len(pkg.GoFiles) == 0 { + return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath) + } + pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID) + if err != nil { + return nil, nil, err + } if _, ok := havePkgs[pkgPath]; !ok { needPkgsSet[pkgPath] = true } @@ -185,6 +216,52 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( return modifiedPkgs, needPkgs, err } +// resolveImport finds the the ID of a package given its import path. +// In particular, it will find the right vendored copy when in GOPATH mode. +func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) { + env, err := state.getEnv() + if err != nil { + return "", err + } + if env["GOMOD"] != "" { + return importPath, nil + } + + searchDir := sourceDir + for { + vendorDir := filepath.Join(searchDir, "vendor") + exists, ok := state.vendorDirs[vendorDir] + if !ok { + info, err := os.Stat(vendorDir) + exists = err == nil && info.IsDir() + state.vendorDirs[vendorDir] = exists + } + + if exists { + vendoredPath := filepath.Join(vendorDir, importPath) + if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() { + // We should probably check for .go files here, but shame on anyone who fools us. + path, ok, err := state.getPkgPath(vendoredPath) + if err != nil { + return "", err + } + if ok { + return path, nil + } + } + } + + // We know we've hit the top of the filesystem when we Dir / and get /, + // or C:\ and get C:\, etc. + next := filepath.Dir(searchDir) + if next == searchDir { + break + } + searchDir = next + } + return importPath, nil +} + func hasTestFiles(p *Package) bool { for _, f := range p.GoFiles { if strings.HasSuffix(f, "_test.go") { @@ -194,44 +271,69 @@ func hasTestFiles(p *Package) bool { return false } -// determineRootDirs returns a mapping from directories code can be contained in to the -// corresponding import path prefixes of those directories. -// Its result is used to try to determine the import path for a package containing -// an overlay file. -func determineRootDirs(cfg *Config) map[string]string { - // Assume modules first: - out, err := invokeGo(cfg, "list", "-m", "-json", "all") +// determineRootDirs returns a mapping from absolute directories that could +// contain code to their corresponding import path prefixes. +func (state *golistState) determineRootDirs() (map[string]string, error) { + env, err := state.getEnv() if err != nil { - return determineRootDirsGOPATH(cfg) + return nil, err + } + if env["GOMOD"] != "" { + state.rootsOnce.Do(func() { + state.rootDirs, state.rootDirsError = state.determineRootDirsModules() + }) + } else { + state.rootsOnce.Do(func() { + state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH() + }) + } + return state.rootDirs, state.rootDirsError +} + +func (state *golistState) determineRootDirsModules() (map[string]string, error) { + // This will only return the root directory for the main module. + // For now we only support overlays in main modules. + // Editing files in the module cache isn't a great idea, so we don't + // plan to ever support that, but editing files in replaced modules + // is something we may want to support. To do that, we'll want to + // do a go list -m to determine the replaced module's module path and + // directory, and then a go list -m {{with .Replace}}{{.Dir}}{{end}} + // from the main module to determine if that module is actually a replacement. + // See bcmills's comment here: https://github.com/golang/go/issues/37629#issuecomment-594179751 + // for more information. + out, err := state.invokeGo("list", "-m", "-json") + if err != nil { + return nil, err } m := map[string]string{} type jsonMod struct{ Path, Dir string } for dec := json.NewDecoder(out); dec.More(); { mod := new(jsonMod) if err := dec.Decode(mod); err != nil { - return m // Give up and return an empty map. Package won't be found for overlay. + return nil, err } if mod.Dir != "" && mod.Path != "" { // This is a valid module; add it to the map. - m[mod.Dir] = mod.Path + absDir, err := filepath.Abs(mod.Dir) + if err != nil { + return nil, err + } + m[absDir] = mod.Path } } - return m + return m, nil } -func determineRootDirsGOPATH(cfg *Config) map[string]string { +func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) { m := map[string]string{} - out, err := invokeGo(cfg, "env", "GOPATH") - if err != nil { - // Could not determine root dir mapping. Everything is best-effort, so just return an empty map. - // When we try to find the import path for a directory, there will be no root-dir match and - // we'll give up. - return m + for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) { + absDir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + m[filepath.Join(absDir, "src")] = "" } - for _, p := range filepath.SplitList(string(bytes.TrimSpace(out.Bytes()))) { - m[filepath.Join(p, "src")] = "" - } - return m + return m, nil } func extractImports(filename string, contents []byte) ([]string, error) { @@ -291,3 +393,46 @@ func extractPackageName(filename string, contents []byte) (string, bool) { } return f.Name.Name, true } + +func commonDir(a []string) string { + seen := make(map[string]bool) + x := append([]string{}, a...) + for _, f := range x { + seen[filepath.Dir(f)] = true + } + if len(seen) > 1 { + log.Fatalf("commonDir saw %v for %v", seen, x) + } + for k := range seen { + // len(seen) == 1 + return k + } + return "" // no files +} + +// It is possible that the files in the disk directory dir have a different package +// name from newName, which is deduced from the overlays. If they all have a different +// package name, and they all have the same package name, then that name becomes +// the package name. +// It returns true if it changes the package name, false otherwise. +func maybeFixPackageName(newName string, pkgOfDir map[string][]*Package, dir string) bool { + names := make(map[string]int) + for _, p := range pkgOfDir[dir] { + names[p.Name]++ + } + if len(names) != 1 { + // some files are in different packages + return false + } + oldName := "" + for k := range names { + oldName = k + } + if newName == oldName { + return false + } + for _, p := range pkgOfDir[dir] { + p.Name = newName + } + return true +} diff --git a/vendor/golang.org/x/tools/go/packages/loadmode_string.go b/vendor/golang.org/x/tools/go/packages/loadmode_string.go index aff94a3fe9..7ea37e7eea 100644 --- a/vendor/golang.org/x/tools/go/packages/loadmode_string.go +++ b/vendor/golang.org/x/tools/go/packages/loadmode_string.go @@ -38,7 +38,7 @@ var modeStrings = []string{ func (mod LoadMode) String() string { m := mod if m == 0 { - return fmt.Sprintf("LoadMode(0)") + return "LoadMode(0)" } var out []string for i, x := range allModes { diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 12ab1e55b0..68039d3a9d 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -19,10 +19,13 @@ import ( "log" "os" "path/filepath" + "reflect" "strings" "sync" "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/packagesinternal" ) // A LoadMode controls the amount of detail to return when loading. @@ -34,6 +37,9 @@ import ( // Load may return more information than requested. type LoadMode int +// TODO(matloob): When a V2 of go/packages is released, rename NeedExportsFile to +// NeedExportFile to make it consistent with the Package field it's adding. + const ( // NeedName adds Name and PkgPath. NeedName LoadMode = 1 << iota @@ -51,7 +57,7 @@ const ( // NeedDeps adds the fields requested by the LoadMode in the packages in Imports. NeedDeps - // NeedExportsFile adds ExportsFile. + // NeedExportsFile adds ExportFile. NeedExportsFile // NeedTypes adds Types, Fset, and IllTyped. @@ -65,6 +71,10 @@ const ( // NeedTypesSizes adds TypesSizes. NeedTypesSizes + + // TypecheckCgo enables full support for type checking cgo. Requires Go 1.15+. + // Modifies CompiledGoFiles and Types, and has no effect on its own. + TypecheckCgo ) const ( @@ -123,6 +133,9 @@ type Config struct { // Env []string + // gocmdRunner guards go command calls from concurrency errors. + gocmdRunner *gocommand.Runner + // BuildFlags is a list of command-line flags to be passed through to // the build system's query tool. BuildFlags []string @@ -160,7 +173,7 @@ type Config struct { Tests bool // Overlay provides a mapping of absolute file paths to file contents. - // If the file with the given path already exists, the parser will use the + // If the file with the given path already exists, the parser will use the // alternative file contents provided by the map. // // Overlays provide incomplete support for when a given file doesn't @@ -249,7 +262,7 @@ type Package struct { GoFiles []string // CompiledGoFiles lists the absolute file paths of the package's source - // files that were presented to the compiler. + // files that are suitable for type checking. // This may differ from GoFiles if files are processed before compilation. CompiledGoFiles []string @@ -292,6 +305,27 @@ type Package struct { // TypesSizes provides the effective size function for types in TypesInfo. TypesSizes types.Sizes + + // forTest is the package under test, if any. + forTest string + + // module is the module information for the package if it exists. + module *packagesinternal.Module +} + +func init() { + packagesinternal.GetForTest = func(p interface{}) string { + return p.(*Package).forTest + } + packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module { + return p.(*Package).module + } + packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner { + return config.(*Config).gocmdRunner + } + packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) { + config.(*Config).gocmdRunner = runner + } } // An Error describes a problem with a package's metadata, syntax, or types. @@ -454,6 +488,9 @@ func newLoader(cfg *Config) *loader { if ld.Config.Env == nil { ld.Config.Env = os.Environ() } + if ld.Config.gocmdRunner == nil { + ld.Config.gocmdRunner = &gocommand.Runner{} + } if ld.Context == nil { ld.Context = context.Background() } @@ -500,12 +537,23 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { if i, found := rootMap[pkg.ID]; found { rootIndex = i } + + // Overlays can invalidate export data. + // TODO(matloob): make this check fine-grained based on dependencies on overlaid files + exportDataInvalid := len(ld.Overlay) > 0 || pkg.ExportFile == "" && pkg.PkgPath != "unsafe" + // This package needs type information if the caller requested types and the package is + // either a root, or it's a non-root and the user requested dependencies ... + needtypes := (ld.Mode&NeedTypes|NeedTypesInfo != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) + // This package needs source if the call requested source (or types info, which implies source) + // and the package is either a root, or itas a non- root and the user requested dependencies... + needsrc := ((ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) || + // ... or if we need types and the exportData is invalid. We fall back to (incompletely) + // typechecking packages from source if they fail to compile. + (ld.Mode&NeedTypes|NeedTypesInfo != 0 && exportDataInvalid)) && pkg.PkgPath != "unsafe" lpkg := &loaderPackage{ Package: pkg, - needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0, - needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0 || - len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files - pkg.ExportFile == "" && pkg.PkgPath != "unsafe", + needtypes: needtypes, + needsrc: needsrc, } ld.pkgs[lpkg.ID] = lpkg if rootIndex >= 0 { @@ -835,6 +883,19 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { Error: appendError, Sizes: ld.sizes, } + if (ld.Mode & TypecheckCgo) != 0 { + // TODO: remove this when we stop supporting 1.14. + rtc := reflect.ValueOf(tc).Elem() + usesCgo := rtc.FieldByName("UsesCgo") + if !usesCgo.IsValid() { + appendError(Error{ + Msg: "TypecheckCgo requires Go 1.15+", + Kind: ListError, + }) + return + } + usesCgo.SetBool(true) + } types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) lpkg.importErrors = nil // no longer needed diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index 882e3b3d8a..cffd7acbee 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -226,7 +226,8 @@ func For(obj types.Object) (Path, error) { // the best paths because non-types may // refer to types, but not the reverse. empty := make([]byte, 0, 48) // initial space - for _, name := range scope.Names() { + names := scope.Names() + for _, name := range names { o := scope.Lookup(name) tname, ok := o.(*types.TypeName) if !ok { @@ -253,7 +254,7 @@ func For(obj types.Object) (Path, error) { // Then inspect everything else: // non-types, and declared methods of defined types. - for _, name := range scope.Names() { + for _, name := range names { o := scope.Lookup(name) path := append(empty, name...) if _, ok := o.(*types.TypeName); !ok { diff --git a/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go new file mode 100644 index 0000000000..26586810c7 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go @@ -0,0 +1,118 @@ +// Copyright 2020 The Go 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 analysisinternal exposes internal-only fields from go/analysis. +package analysisinternal + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/ast/astutil" +) + +func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { + // Get the end position for the type error. + offset, end := fset.PositionFor(start, false).Offset, start + if offset >= len(src) { + return end + } + if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 { + end = start + token.Pos(width) + } + return end +} + +func ZeroValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { + under := typ + if n, ok := typ.(*types.Named); ok { + under = n.Underlying() + } + switch u := under.(type) { + case *types.Basic: + switch { + case u.Info()&types.IsNumeric != 0: + return &ast.BasicLit{Kind: token.INT, Value: "0"} + case u.Info()&types.IsBoolean != 0: + return &ast.Ident{Name: "false"} + case u.Info()&types.IsString != 0: + return &ast.BasicLit{Kind: token.STRING, Value: `""`} + default: + panic("unknown basic type") + } + case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice: + return ast.NewIdent("nil") + case *types.Struct: + texpr := typeExpr(fset, f, pkg, typ) // typ because we want the name here. + if texpr == nil { + return nil + } + return &ast.CompositeLit{ + Type: texpr, + } + case *types.Array: + texpr := typeExpr(fset, f, pkg, u.Elem()) + if texpr == nil { + return nil + } + return &ast.CompositeLit{ + Type: &ast.ArrayType{ + Elt: texpr, + Len: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprintf("%v", u.Len())}, + }, + } + } + return nil +} + +func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { + switch t := typ.(type) { + case *types.Basic: + switch t.Kind() { + case types.UnsafePointer: + return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")} + default: + return ast.NewIdent(t.Name()) + } + case *types.Named: + if t.Obj().Pkg() == pkg { + return ast.NewIdent(t.Obj().Name()) + } + pkgName := t.Obj().Pkg().Name() + // If the file already imports the package under another name, use that. + for _, group := range astutil.Imports(fset, f) { + for _, cand := range group { + if strings.Trim(cand.Path.Value, `"`) == t.Obj().Pkg().Path() { + if cand.Name != nil && cand.Name.Name != "" { + pkgName = cand.Name.Name + } + } + } + } + if pkgName == "." { + return ast.NewIdent(t.Obj().Name()) + } + return &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: ast.NewIdent(t.Obj().Name()), + } + default: + return nil // TODO: anonymous structs, but who does that + } +} + +var GetTypeErrors = func(p interface{}) []types.Error { return nil } +var SetTypeErrors = func(p interface{}, errors []types.Error) {} + +type TypeErrorPass string + +const ( + NoNewVars TypeErrorPass = "nonewvars" + NoResultValues TypeErrorPass = "noresultvalues" + UndeclaredName TypeErrorPass = "undeclaredname" +) diff --git a/vendor/golang.org/x/tools/internal/event/core/event.go b/vendor/golang.org/x/tools/internal/event/core/event.go new file mode 100644 index 0000000000..e37b494915 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/core/event.go @@ -0,0 +1,85 @@ +// Copyright 2019 The Go 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 core provides support for event based telemetry. +package core + +import ( + "fmt" + "time" + + "golang.org/x/tools/internal/event/label" +) + +// Event holds the information about an event of note that ocurred. +type Event struct { + at time.Time + + // As events are often on the stack, storing the first few labels directly + // in the event can avoid an allocation at all for the very common cases of + // simple events. + // The length needs to be large enough to cope with the majority of events + // but no so large as to cause undue stack pressure. + // A log message with two values will use 3 labels (one for each value and + // one for the message itself). + + static [3]label.Label // inline storage for the first few labels + dynamic []label.Label // dynamically sized storage for remaining labels +} + +// eventLabelMap implements label.Map for a the labels of an Event. +type eventLabelMap struct { + event Event +} + +func (ev Event) At() time.Time { return ev.at } + +func (ev Event) Format(f fmt.State, r rune) { + if !ev.at.IsZero() { + fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) + } + for index := 0; ev.Valid(index); index++ { + if l := ev.Label(index); l.Valid() { + fmt.Fprintf(f, "\n\t%v", l) + } + } +} + +func (ev Event) Valid(index int) bool { + return index >= 0 && index < len(ev.static)+len(ev.dynamic) +} + +func (ev Event) Label(index int) label.Label { + if index < len(ev.static) { + return ev.static[index] + } + return ev.dynamic[index-len(ev.static)] +} + +func (ev Event) Find(key label.Key) label.Label { + for _, l := range ev.static { + if l.Key() == key { + return l + } + } + for _, l := range ev.dynamic { + if l.Key() == key { + return l + } + } + return label.Label{} +} + +func MakeEvent(static [3]label.Label, labels []label.Label) Event { + return Event{ + static: static, + dynamic: labels, + } +} + +// CloneEvent event returns a copy of the event with the time adjusted to at. +func CloneEvent(ev Event, at time.Time) Event { + ev.at = at + return ev +} diff --git a/vendor/golang.org/x/tools/internal/event/core/export.go b/vendor/golang.org/x/tools/internal/event/core/export.go new file mode 100644 index 0000000000..05f3a9a579 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/core/export.go @@ -0,0 +1,70 @@ +// Copyright 2019 The Go 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 core + +import ( + "context" + "sync/atomic" + "time" + "unsafe" + + "golang.org/x/tools/internal/event/label" +) + +// Exporter is a function that handles events. +// It may return a modified context and event. +type Exporter func(context.Context, Event, label.Map) context.Context + +var ( + exporter unsafe.Pointer +) + +// SetExporter sets the global exporter function that handles all events. +// The exporter is called synchronously from the event call site, so it should +// return quickly so as not to hold up user code. +func SetExporter(e Exporter) { + p := unsafe.Pointer(&e) + if e == nil { + // &e is always valid, and so p is always valid, but for the early abort + // of ProcessEvent to be efficient it needs to make the nil check on the + // pointer without having to dereference it, so we make the nil function + // also a nil pointer + p = nil + } + atomic.StorePointer(&exporter, p) +} + +// deliver is called to deliver an event to the supplied exporter. +// it will fill in the time. +func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context { + // add the current time to the event + ev.at = time.Now() + // hand the event off to the current exporter + return exporter(ctx, ev, ev) +} + +// Export is called to deliver an event to the global exporter if set. +func Export(ctx context.Context, ev Event) context.Context { + // get the global exporter and abort early if there is not one + exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) + if exporterPtr == nil { + return ctx + } + return deliver(ctx, *exporterPtr, ev) +} + +// ExportPair is called to deliver a start event to the supplied exporter. +// It also returns a function that will deliver the end event to the same +// exporter. +// It will fill in the time. +func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) { + // get the global exporter and abort early if there is not one + exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) + if exporterPtr == nil { + return ctx, func() {} + } + ctx = deliver(ctx, *exporterPtr, begin) + return ctx, func() { deliver(ctx, *exporterPtr, end) } +} diff --git a/vendor/golang.org/x/tools/internal/event/core/fast.go b/vendor/golang.org/x/tools/internal/event/core/fast.go new file mode 100644 index 0000000000..06c1d4615e --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/core/fast.go @@ -0,0 +1,77 @@ +// Copyright 2019 The Go 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 core + +import ( + "context" + + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/event/label" +) + +// Log1 takes a message and one label delivers a log event to the exporter. +// It is a customized version of Print that is faster and does no allocation. +func Log1(ctx context.Context, message string, t1 label.Label) { + Export(ctx, MakeEvent([3]label.Label{ + keys.Msg.Of(message), + t1, + }, nil)) +} + +// Log2 takes a message and two labels and delivers a log event to the exporter. +// It is a customized version of Print that is faster and does no allocation. +func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) { + Export(ctx, MakeEvent([3]label.Label{ + keys.Msg.Of(message), + t1, + t2, + }, nil)) +} + +// Metric1 sends a label event to the exporter with the supplied labels. +func Metric1(ctx context.Context, t1 label.Label) context.Context { + return Export(ctx, MakeEvent([3]label.Label{ + keys.Metric.New(), + t1, + }, nil)) +} + +// Metric2 sends a label event to the exporter with the supplied labels. +func Metric2(ctx context.Context, t1, t2 label.Label) context.Context { + return Export(ctx, MakeEvent([3]label.Label{ + keys.Metric.New(), + t1, + t2, + }, nil)) +} + +// Start1 sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) { + return ExportPair(ctx, + MakeEvent([3]label.Label{ + keys.Start.Of(name), + t1, + }, nil), + MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} + +// Start2 sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) { + return ExportPair(ctx, + MakeEvent([3]label.Label{ + keys.Start.Of(name), + t1, + t2, + }, nil), + MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} diff --git a/vendor/golang.org/x/tools/internal/event/doc.go b/vendor/golang.org/x/tools/internal/event/doc.go new file mode 100644 index 0000000000..5dc6e6babe --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/doc.go @@ -0,0 +1,7 @@ +// Copyright 2019 The Go 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 event provides a set of packages that cover the main +// concepts of telemetry in an implementation agnostic way. +package event diff --git a/vendor/golang.org/x/tools/internal/event/event.go b/vendor/golang.org/x/tools/internal/event/event.go new file mode 100644 index 0000000000..4d55e577d1 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/event.go @@ -0,0 +1,127 @@ +// Copyright 2019 The Go 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 event + +import ( + "context" + + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/event/label" +) + +// Exporter is a function that handles events. +// It may return a modified context and event. +type Exporter func(context.Context, core.Event, label.Map) context.Context + +// SetExporter sets the global exporter function that handles all events. +// The exporter is called synchronously from the event call site, so it should +// return quickly so as not to hold up user code. +func SetExporter(e Exporter) { + core.SetExporter(core.Exporter(e)) +} + +// Log takes a message and a label list and combines them into a single event +// before delivering them to the exporter. +func Log(ctx context.Context, message string, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Msg.Of(message), + }, labels)) +} + +// IsLog returns true if the event was built by the Log function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsLog(ev core.Event) bool { + return ev.Label(0).Key() == keys.Msg +} + +// Error takes a message and a label list and combines them into a single event +// before delivering them to the exporter. It captures the error in the +// delivered event. +func Error(ctx context.Context, message string, err error, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Msg.Of(message), + keys.Err.Of(err), + }, labels)) +} + +// IsError returns true if the event was built by the Error function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsError(ev core.Event) bool { + return ev.Label(0).Key() == keys.Msg && + ev.Label(1).Key() == keys.Err +} + +// Metric sends a label event to the exporter with the supplied labels. +func Metric(ctx context.Context, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Metric.New(), + }, labels)) +} + +// IsMetric returns true if the event was built by the Metric function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsMetric(ev core.Event) bool { + return ev.Label(0).Key() == keys.Metric +} + +// Label sends a label event to the exporter with the supplied labels. +func Label(ctx context.Context, labels ...label.Label) context.Context { + return core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Label.New(), + }, labels)) +} + +// IsLabel returns true if the event was built by the Label function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsLabel(ev core.Event) bool { + return ev.Label(0).Key() == keys.Label +} + +// Start sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) { + return core.ExportPair(ctx, + core.MakeEvent([3]label.Label{ + keys.Start.Of(name), + }, labels), + core.MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} + +// IsStart returns true if the event was built by the Start function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsStart(ev core.Event) bool { + return ev.Label(0).Key() == keys.Start +} + +// IsEnd returns true if the event was built by the End function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsEnd(ev core.Event) bool { + return ev.Label(0).Key() == keys.End +} + +// Detach returns a context without an associated span. +// This allows the creation of spans that are not children of the current span. +func Detach(ctx context.Context) context.Context { + return core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Detach.New(), + }, nil)) +} + +// IsDetach returns true if the event was built by the Detach function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsDetach(ev core.Event) bool { + return ev.Label(0).Key() == keys.Detach +} diff --git a/vendor/golang.org/x/tools/internal/event/keys/keys.go b/vendor/golang.org/x/tools/internal/event/keys/keys.go new file mode 100644 index 0000000000..a02206e301 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/keys/keys.go @@ -0,0 +1,564 @@ +// Copyright 2019 The Go 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 keys + +import ( + "fmt" + "io" + "math" + "strconv" + + "golang.org/x/tools/internal/event/label" +) + +// Value represents a key for untyped values. +type Value struct { + name string + description string +} + +// New creates a new Key for untyped values. +func New(name, description string) *Value { + return &Value{name: name, description: description} +} + +func (k *Value) Name() string { return k.name } +func (k *Value) Description() string { return k.description } + +func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { + fmt.Fprint(w, k.From(l)) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Value) Get(lm label.Map) interface{} { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get a value from a Label. +func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() } + +// Of creates a new Label with this key and the supplied value. +func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) } + +// Tag represents a key for tagging labels that have no value. +// These are used when the existence of the label is the entire information it +// carries, such as marking events to be of a specific kind, or from a specific +// package. +type Tag struct { + name string + description string +} + +// NewTag creates a new Key for tagging labels. +func NewTag(name, description string) *Tag { + return &Tag{name: name, description: description} +} + +func (k *Tag) Name() string { return k.name } +func (k *Tag) Description() string { return k.description } + +func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} + +// New creates a new Label with this key. +func (k *Tag) New() label.Label { return label.OfValue(k, nil) } + +// Int represents a key +type Int struct { + name string + description string +} + +// NewInt creates a new Key for int values. +func NewInt(name, description string) *Int { + return &Int{name: name, description: description} +} + +func (k *Int) Name() string { return k.name } +func (k *Int) Description() string { return k.description } + +func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int) Get(lm label.Map) int { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } + +// Int8 represents a key +type Int8 struct { + name string + description string +} + +// NewInt8 creates a new Key for int8 values. +func NewInt8(name, description string) *Int8 { + return &Int8{name: name, description: description} +} + +func (k *Int8) Name() string { return k.name } +func (k *Int8) Description() string { return k.description } + +func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int8) Get(lm label.Map) int8 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } + +// Int16 represents a key +type Int16 struct { + name string + description string +} + +// NewInt16 creates a new Key for int16 values. +func NewInt16(name, description string) *Int16 { + return &Int16{name: name, description: description} +} + +func (k *Int16) Name() string { return k.name } +func (k *Int16) Description() string { return k.description } + +func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int16) Get(lm label.Map) int16 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } + +// Int32 represents a key +type Int32 struct { + name string + description string +} + +// NewInt32 creates a new Key for int32 values. +func NewInt32(name, description string) *Int32 { + return &Int32{name: name, description: description} +} + +func (k *Int32) Name() string { return k.name } +func (k *Int32) Description() string { return k.description } + +func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int32) Get(lm label.Map) int32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } + +// Int64 represents a key +type Int64 struct { + name string + description string +} + +// NewInt64 creates a new Key for int64 values. +func NewInt64(name, description string) *Int64 { + return &Int64{name: name, description: description} +} + +func (k *Int64) Name() string { return k.name } +func (k *Int64) Description() string { return k.description } + +func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, k.From(l), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int64) Get(lm label.Map) int64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } + +// UInt represents a key +type UInt struct { + name string + description string +} + +// NewUInt creates a new Key for uint values. +func NewUInt(name, description string) *UInt { + return &UInt{name: name, description: description} +} + +func (k *UInt) Name() string { return k.name } +func (k *UInt) Description() string { return k.description } + +func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt) Get(lm label.Map) uint { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } + +// UInt8 represents a key +type UInt8 struct { + name string + description string +} + +// NewUInt8 creates a new Key for uint8 values. +func NewUInt8(name, description string) *UInt8 { + return &UInt8{name: name, description: description} +} + +func (k *UInt8) Name() string { return k.name } +func (k *UInt8) Description() string { return k.description } + +func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt8) Get(lm label.Map) uint8 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } + +// UInt16 represents a key +type UInt16 struct { + name string + description string +} + +// NewUInt16 creates a new Key for uint16 values. +func NewUInt16(name, description string) *UInt16 { + return &UInt16{name: name, description: description} +} + +func (k *UInt16) Name() string { return k.name } +func (k *UInt16) Description() string { return k.description } + +func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt16) Get(lm label.Map) uint16 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } + +// UInt32 represents a key +type UInt32 struct { + name string + description string +} + +// NewUInt32 creates a new Key for uint32 values. +func NewUInt32(name, description string) *UInt32 { + return &UInt32{name: name, description: description} +} + +func (k *UInt32) Name() string { return k.name } +func (k *UInt32) Description() string { return k.description } + +func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt32) Get(lm label.Map) uint32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } + +// UInt64 represents a key +type UInt64 struct { + name string + description string +} + +// NewUInt64 creates a new Key for uint64 values. +func NewUInt64(name, description string) *UInt64 { + return &UInt64{name: name, description: description} +} + +func (k *UInt64) Name() string { return k.name } +func (k *UInt64) Description() string { return k.description } + +func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, k.From(l), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt64) Get(lm label.Map) uint64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } + +// Float32 represents a key +type Float32 struct { + name string + description string +} + +// NewFloat32 creates a new Key for float32 values. +func NewFloat32(name, description string) *Float32 { + return &Float32{name: name, description: description} +} + +func (k *Float32) Name() string { return k.name } +func (k *Float32) Description() string { return k.description } + +func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Float32) Of(v float32) label.Label { + return label.Of64(k, uint64(math.Float32bits(v))) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Float32) Get(lm label.Map) float32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Float32) From(t label.Label) float32 { + return math.Float32frombits(uint32(t.Unpack64())) +} + +// Float64 represents a key +type Float64 struct { + name string + description string +} + +// NewFloat64 creates a new Key for int64 values. +func NewFloat64(name, description string) *Float64 { + return &Float64{name: name, description: description} +} + +func (k *Float64) Name() string { return k.name } +func (k *Float64) Description() string { return k.description } + +func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Float64) Of(v float64) label.Label { + return label.Of64(k, math.Float64bits(v)) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Float64) Get(lm label.Map) float64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Float64) From(t label.Label) float64 { + return math.Float64frombits(t.Unpack64()) +} + +// String represents a key +type String struct { + name string + description string +} + +// NewString creates a new Key for int64 values. +func NewString(name, description string) *String { + return &String{name: name, description: description} +} + +func (k *String) Name() string { return k.name } +func (k *String) Description() string { return k.description } + +func (k *String) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendQuote(buf, k.From(l))) +} + +// Of creates a new Label with this key and the supplied value. +func (k *String) Of(v string) label.Label { return label.OfString(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *String) Get(lm label.Map) string { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return "" +} + +// From can be used to get a value from a Label. +func (k *String) From(t label.Label) string { return t.UnpackString() } + +// Boolean represents a key +type Boolean struct { + name string + description string +} + +// NewBoolean creates a new Key for bool values. +func NewBoolean(name, description string) *Boolean { + return &Boolean{name: name, description: description} +} + +func (k *Boolean) Name() string { return k.name } +func (k *Boolean) Description() string { return k.description } + +func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendBool(buf, k.From(l))) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Boolean) Of(v bool) label.Label { + if v { + return label.Of64(k, 1) + } + return label.Of64(k, 0) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Boolean) Get(lm label.Map) bool { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return false +} + +// From can be used to get a value from a Label. +func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } + +// Error represents a key +type Error struct { + name string + description string +} + +// NewError creates a new Key for int64 values. +func NewError(name, description string) *Error { + return &Error{name: name, description: description} +} + +func (k *Error) Name() string { return k.name } +func (k *Error) Description() string { return k.description } + +func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { + io.WriteString(w, k.From(l).Error()) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Error) Get(lm label.Map) error { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get a value from a Label. +func (k *Error) From(t label.Label) error { + err, _ := t.UnpackValue().(error) + return err +} diff --git a/vendor/golang.org/x/tools/internal/event/keys/standard.go b/vendor/golang.org/x/tools/internal/event/keys/standard.go new file mode 100644 index 0000000000..7e95866592 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/keys/standard.go @@ -0,0 +1,22 @@ +// Copyright 2020 The Go 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 keys + +var ( + // Msg is a key used to add message strings to label lists. + Msg = NewString("message", "a readable message") + // Label is a key used to indicate an event adds labels to the context. + Label = NewTag("label", "a label context marker") + // Start is used for things like traces that have a name. + Start = NewString("start", "span start") + // Metric is a key used to indicate an event records metrics. + End = NewTag("end", "a span end marker") + // Metric is a key used to indicate an event records metrics. + Detach = NewTag("detach", "a span detach marker") + // Err is a key used to add error values to label lists. + Err = NewError("error", "an error that occurred") + // Metric is a key used to indicate an event records metrics. + Metric = NewTag("metric", "a metric event marker") +) diff --git a/vendor/golang.org/x/tools/internal/event/label/label.go b/vendor/golang.org/x/tools/internal/event/label/label.go new file mode 100644 index 0000000000..b55c12eb25 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/label/label.go @@ -0,0 +1,213 @@ +// Copyright 2019 The Go 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 label + +import ( + "fmt" + "io" + "reflect" + "unsafe" +) + +// Key is used as the identity of a Label. +// Keys are intended to be compared by pointer only, the name should be unique +// for communicating with external systems, but it is not required or enforced. +type Key interface { + // Name returns the key name. + Name() string + // Description returns a string that can be used to describe the value. + Description() string + + // Format is used in formatting to append the value of the label to the + // supplied buffer. + // The formatter may use the supplied buf as a scratch area to avoid + // allocations. + Format(w io.Writer, buf []byte, l Label) +} + +// Label holds a key and value pair. +// It is normally used when passing around lists of labels. +type Label struct { + key Key + packed uint64 + untyped interface{} +} + +// Map is the interface to a collection of Labels indexed by key. +type Map interface { + // Find returns the label that matches the supplied key. + Find(key Key) Label +} + +// List is the interface to something that provides an iterable +// list of labels. +// Iteration should start from 0 and continue until Valid returns false. +type List interface { + // Valid returns true if the index is within range for the list. + // It does not imply the label at that index will itself be valid. + Valid(index int) bool + // Label returns the label at the given index. + Label(index int) Label +} + +// list implements LabelList for a list of Labels. +type list struct { + labels []Label +} + +// filter wraps a LabelList filtering out specific labels. +type filter struct { + keys []Key + underlying List +} + +// listMap implements LabelMap for a simple list of labels. +type listMap struct { + labels []Label +} + +// mapChain implements LabelMap for a list of underlying LabelMap. +type mapChain struct { + maps []Map +} + +// OfValue creates a new label from the key and value. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} } + +// UnpackValue assumes the label was built using LabelOfValue and returns the value +// that was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) UnpackValue() interface{} { return t.untyped } + +// Of64 creates a new label from a key and a uint64. This is often +// used for non uint64 values that can be packed into a uint64. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} } + +// Unpack64 assumes the label was built using LabelOf64 and returns the value that +// was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) Unpack64() uint64 { return t.packed } + +// OfString creates a new label from a key and a string. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func OfString(k Key, v string) Label { + hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) + return Label{ + key: k, + packed: uint64(hdr.Len), + untyped: unsafe.Pointer(hdr.Data), + } +} + +// UnpackString assumes the label was built using LabelOfString and returns the +// value that was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) UnpackString() string { + var v string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) + hdr.Data = uintptr(t.untyped.(unsafe.Pointer)) + hdr.Len = int(t.packed) + return *(*string)(unsafe.Pointer(hdr)) +} + +// Valid returns true if the Label is a valid one (it has a key). +func (t Label) Valid() bool { return t.key != nil } + +// Key returns the key of this Label. +func (t Label) Key() Key { return t.key } + +// Format is used for debug printing of labels. +func (t Label) Format(f fmt.State, r rune) { + if !t.Valid() { + io.WriteString(f, `nil`) + return + } + io.WriteString(f, t.Key().Name()) + io.WriteString(f, "=") + var buf [128]byte + t.Key().Format(f, buf[:0], t) +} + +func (l *list) Valid(index int) bool { + return index >= 0 && index < len(l.labels) +} + +func (l *list) Label(index int) Label { + return l.labels[index] +} + +func (f *filter) Valid(index int) bool { + return f.underlying.Valid(index) +} + +func (f *filter) Label(index int) Label { + l := f.underlying.Label(index) + for _, f := range f.keys { + if l.Key() == f { + return Label{} + } + } + return l +} + +func (lm listMap) Find(key Key) Label { + for _, l := range lm.labels { + if l.Key() == key { + return l + } + } + return Label{} +} + +func (c mapChain) Find(key Key) Label { + for _, src := range c.maps { + l := src.Find(key) + if l.Valid() { + return l + } + } + return Label{} +} + +var emptyList = &list{} + +func NewList(labels ...Label) List { + if len(labels) == 0 { + return emptyList + } + return &list{labels: labels} +} + +func Filter(l List, keys ...Key) List { + if len(keys) == 0 { + return l + } + return &filter{keys: keys, underlying: l} +} + +func NewMap(labels ...Label) Map { + return listMap{labels: labels} +} + +func MergeMaps(srcs ...Map) Map { + var nonNil []Map + for _, src := range srcs { + if src != nil { + nonNil = append(nonNil, src) + } + } + if len(nonNil) == 1 { + return nonNil[0] + } + return mapChain{maps: nonNil} +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go index 7219c8e9ff..9887f7e7a0 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go @@ -14,14 +14,14 @@ import ( "sync" ) -// TraverseLink is used as a return value from WalkFuncs to indicate that the +// ErrTraverseLink is used as a return value from WalkFuncs to indicate that the // symlink named in the call may be traversed. -var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory") +var ErrTraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory") -// SkipFiles is a used as a return value from WalkFuncs to indicate that the +// ErrSkipFiles is a used as a return value from WalkFuncs to indicate that the // callback should not be called for any other files in the current directory. // Child directories will still be traversed. -var SkipFiles = errors.New("fastwalk: skip remaining files in directory") +var ErrSkipFiles = errors.New("fastwalk: skip remaining files in directory") // Walk is a faster implementation of filepath.Walk. // @@ -167,7 +167,7 @@ func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error { err := w.fn(joined, typ) if typ == os.ModeSymlink { - if err == TraverseLink { + if err == ErrTraverseLink { // Set callbackDone so we don't call it twice for both the // symlink-as-symlink and the symlink-as-directory later: w.enqueue(walkItem{dir: joined, callbackDone: true}) diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go index a906b87595..b0d6327a9e 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go @@ -26,7 +26,7 @@ func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) e continue } if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil { - if err == SkipFiles { + if err == ErrSkipFiles { skipFiles = true continue } diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go index 3369b1a0b2..5901a8f616 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go @@ -66,7 +66,7 @@ func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) e continue } if err := fn(dirName, name, typ); err != nil { - if err == SkipFiles { + if err == ErrSkipFiles { skipFiles = true continue } @@ -76,8 +76,9 @@ func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) e } func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) { - // golang.org/issue/15653 - dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0])) + // golang.org/issue/37269 + dirent := &syscall.Dirent{} + copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(dirent))[:], buf) if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v { panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v)) } diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go new file mode 100644 index 0000000000..9aa7984561 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -0,0 +1,187 @@ +// Copyright 2020 The Go 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 gocommand is a helper for calling the go command. +package gocommand + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "regexp" + "strings" + "sync" + "time" + + "golang.org/x/tools/internal/event" +) + +// An Runner will run go command invocations and serialize +// them if it sees a concurrency error. +type Runner struct { + // LoadMu guards packages.Load calls and associated state. + loadMu sync.Mutex + serializeLoads int +} + +// 1.13: go: updates to go.mod needed, but contents have changed +// 1.14: go: updating go.mod: existing contents have changed since last read +var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) + +// Run calls Runner.RunRaw, serializing requests if they fight over +// go.mod changes. +func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { + stdout, _, friendly, _ := runner.RunRaw(ctx, inv) + return stdout, friendly +} + +// RunRaw calls Invocation.runRaw, serializing requests if they fight over +// go.mod changes. +func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { + // We want to run invocations concurrently as much as possible. However, + // if go.mod updates are needed, only one can make them and the others will + // fail. We need to retry in those cases, but we don't want to thrash so + // badly we never recover. To avoid that, once we've seen one concurrency + // error, start serializing everything until the backlog has cleared out. + runner.loadMu.Lock() + var locked bool // If true, we hold the mutex and have incremented. + if runner.serializeLoads == 0 { + runner.loadMu.Unlock() + } else { + locked = true + runner.serializeLoads++ + } + defer func() { + if locked { + runner.serializeLoads-- + runner.loadMu.Unlock() + } + }() + + for { + stdout, stderr, friendlyErr, err := inv.runRaw(ctx) + if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { + return stdout, stderr, friendlyErr, err + } + event.Error(ctx, "Load concurrency error, will retry serially", err) + if !locked { + runner.loadMu.Lock() + runner.serializeLoads++ + locked = true + } + } +} + +// An Invocation represents a call to the go command. +type Invocation struct { + Verb string + Args []string + BuildFlags []string + Env []string + WorkingDir string + Logf func(format string, args ...interface{}) +} + +// RunRaw is like RunPiped, but also returns the raw stderr and error for callers +// that want to do low-level error handling/recovery. +func (i *Invocation) runRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) { + stdout = &bytes.Buffer{} + stderr = &bytes.Buffer{} + rawError = i.RunPiped(ctx, stdout, stderr) + if rawError != nil { + friendlyError = rawError + // Check for 'go' executable not being found. + if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { + friendlyError = fmt.Errorf("go command required, not found: %v", ee) + } + if ctx.Err() != nil { + friendlyError = ctx.Err() + } + friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) + } + return +} + +// RunPiped is like Run, but relies on the given stdout/stderr +func (i *Invocation) RunPiped(ctx context.Context, stdout, stderr io.Writer) error { + log := i.Logf + if log == nil { + log = func(string, ...interface{}) {} + } + + goArgs := []string{i.Verb} + switch i.Verb { + case "mod": + // mod needs the sub-verb before build flags. + goArgs = append(goArgs, i.Args[0]) + goArgs = append(goArgs, i.BuildFlags...) + goArgs = append(goArgs, i.Args[1:]...) + case "env": + // env doesn't take build flags. + goArgs = append(goArgs, i.Args...) + default: + goArgs = append(goArgs, i.BuildFlags...) + goArgs = append(goArgs, i.Args...) + } + cmd := exec.Command("go", goArgs...) + cmd.Stdout = stdout + cmd.Stderr = stderr + // On darwin the cwd gets resolved to the real path, which breaks anything that + // expects the working directory to keep the original path, including the + // go command when dealing with modules. + // The Go stdlib has a special feature where if the cwd and the PWD are the + // same node then it trusts the PWD, so by setting it in the env for the child + // process we fix up all the paths returned by the go command. + cmd.Env = append(os.Environ(), i.Env...) + if i.WorkingDir != "" { + cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) + cmd.Dir = i.WorkingDir + } + + defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) + + return runCmdContext(ctx, cmd) +} + +// runCmdContext is like exec.CommandContext except it sends os.Interrupt +// before os.Kill. +func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { + if err := cmd.Start(); err != nil { + return err + } + resChan := make(chan error, 1) + go func() { + resChan <- cmd.Wait() + }() + + select { + case err := <-resChan: + return err + case <-ctx.Done(): + } + // Cancelled. Interrupt and see if it ends voluntarily. + cmd.Process.Signal(os.Interrupt) + select { + case err := <-resChan: + return err + case <-time.After(time.Second): + } + // Didn't shut down in response to interrupt. Kill it hard. + cmd.Process.Kill() + return <-resChan +} + +func cmdDebugStr(cmd *exec.Cmd) string { + env := make(map[string]string) + for _, kv := range cmd.Env { + split := strings.Split(kv, "=") + k, v := split[0], split[1] + env[k] = v + } + + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args) +} diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go index d067562289..390cb9db79 100644 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -23,8 +23,10 @@ import ( // Options controls the behavior of a Walk call. type Options struct { - Debug bool // Enable debug logging - ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules. + // If Logf is non-nil, debug logging is enabled through this function. + Logf func(format string, args ...interface{}) + // Search module caches. Also disables legacy goimports ignore rules. + ModulesEnabled bool } // RootType indicates the type of a Root. @@ -80,14 +82,14 @@ func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root // walkDir creates a walker and starts fastwalk with this walker. func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { if _, err := os.Stat(root.Path); os.IsNotExist(err) { - if opts.Debug { - log.Printf("skipping nonexistent directory: %v", root.Path) + if opts.Logf != nil { + opts.Logf("skipping nonexistent directory: %v", root.Path) } return } start := time.Now() - if opts.Debug { - log.Printf("gopathwalk: scanning %s", root.Path) + if opts.Logf != nil { + opts.Logf("gopathwalk: scanning %s", root.Path) } w := &walker{ root: root, @@ -100,8 +102,8 @@ func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) log.Printf("gopathwalk: scanning directory %v: %v", root.Path, err) } - if opts.Debug { - log.Printf("gopathwalk: scanned %s in %v", root.Path, time.Since(start)) + if opts.Logf != nil { + opts.Logf("gopathwalk: scanned %s in %v", root.Path, time.Since(start)) } } @@ -130,11 +132,11 @@ func (w *walker) init() { full := filepath.Join(w.root.Path, p) if fi, err := os.Stat(full); err == nil { w.ignoredDirs = append(w.ignoredDirs, fi) - if w.opts.Debug { - log.Printf("Directory added to ignore list: %s", full) + if w.opts.Logf != nil { + w.opts.Logf("Directory added to ignore list: %s", full) } - } else if w.opts.Debug { - log.Printf("Error statting ignored directory: %v", err) + } else if w.opts.Logf != nil { + w.opts.Logf("Error statting ignored directory: %v", err) } } } @@ -145,11 +147,11 @@ func (w *walker) init() { func (w *walker) getIgnoredDirs(path string) []string { file := filepath.Join(path, ".goimportsignore") slurp, err := ioutil.ReadFile(file) - if w.opts.Debug { + if w.opts.Logf != nil { if err != nil { - log.Print(err) + w.opts.Logf("%v", err) } else { - log.Printf("Read %s", file) + w.opts.Logf("Read %s", file) } } if err != nil { @@ -189,14 +191,14 @@ func (w *walker) walk(path string, typ os.FileMode) error { if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { // Doesn't make sense to have regular files // directly in your $GOPATH/src or $GOROOT/src. - return fastwalk.SkipFiles + return fastwalk.ErrSkipFiles } if !strings.HasSuffix(path, ".go") { return nil } w.add(w.root, dir) - return fastwalk.SkipFiles + return fastwalk.ErrSkipFiles } if typ == os.ModeDir { base := filepath.Base(path) @@ -224,7 +226,7 @@ func (w *walker) walk(path string, typ os.FileMode) error { return nil } if w.shouldTraverse(dir, fi) { - return fastwalk.TraverseLink + return fastwalk.ErrTraverseLink } } return nil diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index 818d44383f..3a53bb6bf7 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -14,7 +14,6 @@ import ( "go/token" "io/ioutil" "os" - "os/exec" "path" "path/filepath" "reflect" @@ -22,12 +21,11 @@ import ( "strconv" "strings" "sync" - "time" "unicode" "unicode/utf8" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/packages" + "golang.org/x/tools/internal/gocommand" "golang.org/x/tools/internal/gopathwalk" ) @@ -52,7 +50,8 @@ var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool return }, func(_ *ProcessEnv, importPath string) (num int, ok bool) { - if strings.Contains(importPath, ".") { + firstComponent := strings.Split(importPath, "/")[0] + if strings.Contains(firstComponent, ".") { return 1, true } return @@ -82,7 +81,8 @@ type ImportFix struct { // IdentName is the identifier that this fix will add or remove. IdentName string // FixType is the type of fix this is (AddImport, DeleteImport, SetImportName). - FixType ImportFixType + FixType ImportFixType + Relevance int // see pkg } // An ImportInfo represents a single import statement. @@ -263,7 +263,7 @@ type pass struct { // loadPackageNames saves the package names for everything referenced by imports. func (p *pass) loadPackageNames(imports []*ImportInfo) error { - if p.env.Debug { + if p.env.Logf != nil { p.env.Logf("loading package names for %v packages", len(imports)) defer func() { p.env.Logf("done loading package names for %v packages", len(imports)) @@ -335,7 +335,7 @@ func (p *pass) load() ([]*ImportFix, bool) { if p.loadRealPackageNames { err := p.loadPackageNames(append(imports, p.candidates...)) if err != nil { - if p.env.Debug { + if p.env.Logf != nil { p.env.Logf("loading package names: %v", err) } return nil, false @@ -529,7 +529,7 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv return nil, err } srcDir := filepath.Dir(abs) - if env.Debug { + if env.Logf != nil { env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir) } @@ -537,7 +537,7 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv // derive package names from import paths, see if the file is already // complete. We can't add any imports yet, because we don't know // if missing references are actually package vars. - p := &pass{fset: fset, f: f, srcDir: srcDir} + p := &pass{fset: fset, f: f, srcDir: srcDir, env: env} if fixes, done := p.load(); done { return fixes, nil } @@ -559,8 +559,7 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv } // Third pass: get real package names where we had previously used - // the naive algorithm. This is the first step that will use the - // environment, so we provide it here for the first time. + // the naive algorithm. p = &pass{fset: fset, f: f, srcDir: srcDir, env: env} p.loadRealPackageNames = true p.otherFiles = otherFiles @@ -585,62 +584,86 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv return fixes, nil } -// getCandidatePkgs returns the list of pkgs that are accessible from filename, -// optionall filtered to only packages named pkgName. -func getCandidatePkgs(pkgName, filename string, env *ProcessEnv) ([]*pkg, error) { - // TODO(heschi): filter out current package. (Don't forget x_test can import x.) +// Highest relevance, used for the standard library. Chosen arbitrarily to +// match pre-existing gopls code. +const MaxRelevance = 7 - var result []*pkg +// getCandidatePkgs works with the passed callback to find all acceptable packages. +// It deduplicates by import path, and uses a cached stdlib rather than reading +// from disk. +func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error { + notSelf := func(p *pkg) bool { + return p.packageName != filePkg || p.dir != filepath.Dir(filename) + } // Start off with the standard library. - for importPath := range stdlib { - if pkgName != "" && path.Base(importPath) != pkgName { - continue - } - result = append(result, &pkg{ + for importPath, exports := range stdlib { + p := &pkg{ dir: filepath.Join(env.GOROOT, "src", importPath), importPathShort: importPath, packageName: path.Base(importPath), - relevance: 0, - }) - } - - // Exclude goroot results -- getting them is relatively expensive, not cached, - // and generally redundant with the in-memory version. - exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT} - // Only the go/packages resolver uses the first argument, and nobody uses that resolver. - scannedPkgs, err := env.GetResolver().scan(nil, true, exclude) - if err != nil { - return nil, err + relevance: MaxRelevance, + } + if notSelf(p) && wrappedCallback.packageNameLoaded(p) { + wrappedCallback.exportsLoaded(p, exports) + } } + var mu sync.Mutex dupCheck := map[string]struct{}{} - for _, pkg := range scannedPkgs { - if pkgName != "" && pkg.packageName != pkgName { - continue - } - if !canUse(filename, pkg.dir) { - continue - } - if _, ok := dupCheck[pkg.importPathShort]; ok { - continue - } - dupCheck[pkg.importPathShort] = struct{}{} - result = append(result, pkg) + + scanFilter := &scanCallback{ + rootFound: func(root gopathwalk.Root) bool { + // Exclude goroot results -- getting them is relatively expensive, not cached, + // and generally redundant with the in-memory version. + return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root) + }, + dirFound: wrappedCallback.dirFound, + packageNameLoaded: func(pkg *pkg) bool { + mu.Lock() + defer mu.Unlock() + if _, ok := dupCheck[pkg.importPathShort]; ok { + return false + } + dupCheck[pkg.importPathShort] = struct{}{} + return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg) + }, + exportsLoaded: func(pkg *pkg, exports []string) { + // If we're an x_test, load the package under test's test variant. + if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) { + var err error + _, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true) + if err != nil { + return + } + } + wrappedCallback.exportsLoaded(pkg, exports) + }, } + return env.GetResolver().scan(ctx, scanFilter) +} - // Sort first by relevance, then by package name, with import path as a tiebreaker. - sort.Slice(result, func(i, j int) bool { - pi, pj := result[i], result[j] - if pi.relevance != pj.relevance { - return pi.relevance < pj.relevance - } - if pi.packageName != pj.packageName { - return pi.packageName < pj.packageName - } - return pi.importPathShort < pj.importPathShort - }) +func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) map[string]int { + result := make(map[string]int) + for _, path := range paths { + result[path] = env.GetResolver().scoreImportPath(ctx, path) + } + return result +} - return result, nil +func PrimeCache(ctx context.Context, env *ProcessEnv) error { + // Fully scan the disk for directories, but don't actually read any Go files. + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + return false + }, + packageNameLoaded: func(pkg *pkg) bool { + return false + }, + } + return getCandidatePkgs(ctx, callback, "", "", env) } func candidateImportName(pkg *pkg) string { @@ -651,23 +674,37 @@ func candidateImportName(pkg *pkg) string { } // getAllCandidates gets all of the candidates to be imported, regardless of if they are needed. -func getAllCandidates(filename string, env *ProcessEnv) ([]ImportFix, error) { - pkgs, err := getCandidatePkgs("", filename, env) - if err != nil { - return nil, err +func getAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + if !canUse(filename, pkg.dir) { + return false + } + // Try the assumed package name first, then a simpler path match + // in case of packages named vN, which are not uncommon. + return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) || + strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix) + }, + packageNameLoaded: func(pkg *pkg) bool { + if !strings.HasPrefix(pkg.packageName, searchPrefix) { + return false + } + wrapped(ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }) + return false + }, } - result := make([]ImportFix, 0, len(pkgs)) - for _, pkg := range pkgs { - result = append(result, ImportFix{ - StmtInfo: ImportInfo{ - ImportPath: pkg.importPathShort, - Name: candidateImportName(pkg), - }, - IdentName: pkg.packageName, - FixType: AddImport, - }) - } - return result, nil + return getCandidatePkgs(ctx, callback, filename, filePkg, env) } // A PackageExport is a package and its exports. @@ -676,64 +713,63 @@ type PackageExport struct { Exports []string } -func getPackageExports(completePackage, filename string, env *ProcessEnv) ([]PackageExport, error) { - pkgs, err := getCandidatePkgs(completePackage, filename, env) - if err != nil { - return nil, err +func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + return pkgIsCandidate(filename, references{searchPkg: nil}, pkg) + }, + packageNameLoaded: func(pkg *pkg) bool { + return pkg.packageName == searchPkg + }, + exportsLoaded: func(pkg *pkg, exports []string) { + sort.Strings(exports) + wrapped(PackageExport{ + Fix: &ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }, + Exports: exports, + }) + }, } - - results := make([]PackageExport, 0, len(pkgs)) - for _, pkg := range pkgs { - fix := &ImportFix{ - StmtInfo: ImportInfo{ - ImportPath: pkg.importPathShort, - Name: candidateImportName(pkg), - }, - IdentName: pkg.packageName, - FixType: AddImport, - } - var exports []string - if e, ok := stdlib[pkg.importPathShort]; ok { - exports = e - } else { - exports, err = loadExportsForPackage(context.Background(), env, completePackage, pkg) - if err != nil { - if env.Debug { - env.Logf("while completing %q, error loading exports from %q: %v", completePackage, pkg.importPathShort, err) - } - continue - } - } - sort.Strings(exports) - results = append(results, PackageExport{ - Fix: fix, - Exports: exports, - }) - } - - return results, nil + return getCandidatePkgs(ctx, callback, filename, filePkg, env) } // ProcessEnv contains environment variables and settings that affect the use of // the go command, the go/build package, etc. type ProcessEnv struct { LocalPrefix string - Debug bool + + GocmdRunner *gocommand.Runner + + BuildFlags []string // If non-empty, these will be used instead of the // process-wide values. GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS, GOSUMDB string WorkingDir string - // If true, use go/packages regardless of the environment. - ForceGoPackages bool - - // Logf is the default logger for the ProcessEnv. + // If Logf is non-nil, debug logging is enabled through this function. Logf func(format string, args ...interface{}) resolver Resolver } +// CopyConfig copies the env's configuration into a new env. +func (e *ProcessEnv) CopyConfig() *ProcessEnv { + copy := *e + copy.resolver = nil + return © +} + func (e *ProcessEnv) env() []string { env := os.Environ() add := func(k, v string) { @@ -757,28 +793,15 @@ func (e *ProcessEnv) GetResolver() Resolver { if e.resolver != nil { return e.resolver } - if e.ForceGoPackages { - e.resolver = &goPackagesResolver{env: e} - return e.resolver - } - - out, err := e.invokeGo("env", "GOMOD") + out, err := e.invokeGo(context.TODO(), "env", "GOMOD") if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 { - e.resolver = &gopathResolver{env: e} + e.resolver = newGopathResolver(e) return e.resolver } - e.resolver = &ModuleResolver{env: e} + e.resolver = newModuleResolver(e) return e.resolver } -func (e *ProcessEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config { - return &packages.Config{ - Mode: mode, - Dir: e.WorkingDir, - Env: e.env(), - } -} - func (e *ProcessEnv) buildContext() *build.Context { ctx := build.Default ctx.GOROOT = e.GOROOT @@ -801,37 +824,24 @@ func (e *ProcessEnv) buildContext() *build.Context { return &ctx } -func (e *ProcessEnv) invokeGo(args ...string) (*bytes.Buffer, error) { - cmd := exec.Command("go", args...) - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - cmd.Stdout = stdout - cmd.Stderr = stderr - cmd.Env = e.env() - cmd.Dir = e.WorkingDir - - if e.Debug { - defer func(start time.Time) { e.Logf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) +func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) { + inv := gocommand.Invocation{ + Verb: verb, + Args: args, + BuildFlags: e.BuildFlags, + Env: e.env(), + Logf: e.Logf, + WorkingDir: e.WorkingDir, } - if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("running go: %v (stderr:\n%s)", err, stderr) - } - return stdout, nil -} - -func cmdDebugStr(cmd *exec.Cmd) string { - env := make(map[string]string) - for _, kv := range cmd.Env { - split := strings.Split(kv, "=") - k, v := split[0], split[1] - env[k] = v - } - - return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args) + return e.GocmdRunner.Run(ctx, inv) } func addStdlibCandidates(pass *pass, refs references) { add := func(pkg string) { + // Prevent self-imports. + if path.Base(pkg) == pass.f.Name.Name && filepath.Join(pass.env.GOROOT, "src", pkg) == pass.srcDir { + return + } exports := copyExports(stdlib[pkg]) pass.addCandidate( &ImportInfo{ImportPath: pkg}, @@ -856,94 +866,65 @@ func addStdlibCandidates(pass *pass, refs references) { type Resolver interface { // loadPackageNames loads the package names in importPaths. loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) - // scan finds (at least) the packages satisfying refs. If loadNames is true, - // package names will be set on the results, and dirs whose package name - // could not be determined will be excluded. - scan(refs references, loadNames bool, exclude []gopathwalk.RootType) ([]*pkg, error) + // scan works with callback to search for packages. See scanCallback for details. + scan(ctx context.Context, callback *scanCallback) error // loadExports returns the set of exported symbols in the package at dir. // loadExports may be called concurrently. - loadExports(ctx context.Context, pkg *pkg) (string, []string, error) + loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) + // scoreImportPath returns the relevance for an import path. + scoreImportPath(ctx context.Context, path string) int ClearForNewScan() } -// gopackagesResolver implements resolver for GOPATH and module workspaces using go/packages. -type goPackagesResolver struct { - env *ProcessEnv -} - -func (r *goPackagesResolver) ClearForNewScan() {} - -func (r *goPackagesResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - if len(importPaths) == 0 { - return nil, nil - } - cfg := r.env.newPackagesConfig(packages.LoadFiles) - pkgs, err := packages.Load(cfg, importPaths...) - if err != nil { - return nil, err - } - names := map[string]string{} - for _, pkg := range pkgs { - names[VendorlessPath(pkg.PkgPath)] = pkg.Name - } - // We may not have found all the packages. Guess the rest. - for _, path := range importPaths { - if _, ok := names[path]; ok { - continue - } - names[path] = ImportPathToAssumedName(path) - } - return names, nil - -} - -func (r *goPackagesResolver) scan(refs references, _ bool, _ []gopathwalk.RootType) ([]*pkg, error) { - var loadQueries []string - for pkgName := range refs { - loadQueries = append(loadQueries, "iamashamedtousethedisabledqueryname="+pkgName) - } - sort.Strings(loadQueries) - cfg := r.env.newPackagesConfig(packages.LoadFiles) - goPackages, err := packages.Load(cfg, loadQueries...) - if err != nil { - return nil, err - } - - var scan []*pkg - for _, goPackage := range goPackages { - scan = append(scan, &pkg{ - dir: filepath.Dir(goPackage.CompiledGoFiles[0]), - importPathShort: VendorlessPath(goPackage.PkgPath), - goPackage: goPackage, - packageName: goPackage.Name, - }) - } - return scan, nil -} - -func (r *goPackagesResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { - if pkg.goPackage == nil { - return "", nil, fmt.Errorf("goPackage not set") - } - var exports []string - fset := token.NewFileSet() - for _, fname := range pkg.goPackage.CompiledGoFiles { - f, err := parser.ParseFile(fset, fname, nil, 0) - if err != nil { - return "", nil, fmt.Errorf("parsing %s: %v", fname, err) - } - for name := range f.Scope.Objects { - if ast.IsExported(name) { - exports = append(exports, name) - } - } - } - return pkg.goPackage.Name, exports, nil +// A scanCallback controls a call to scan and receives its results. +// In general, minor errors will be silently discarded; a user should not +// expect to receive a full series of calls for everything. +type scanCallback struct { + // rootFound is called before scanning a new root dir. If it returns true, + // the root will be scanned. Returning false will not necessarily prevent + // directories from that root making it to dirFound. + rootFound func(gopathwalk.Root) bool + // dirFound is called when a directory is found that is possibly a Go package. + // pkg will be populated with everything except packageName. + // If it returns true, the package's name will be loaded. + dirFound func(pkg *pkg) bool + // packageNameLoaded is called when a package is found and its name is loaded. + // If it returns true, the package's exports will be loaded. + packageNameLoaded func(pkg *pkg) bool + // exportsLoaded is called when a package's exports have been loaded. + exportsLoaded func(pkg *pkg, exports []string) } func addExternalCandidates(pass *pass, refs references, filename string) error { - dirScan, err := pass.env.GetResolver().scan(refs, false, nil) + var mu sync.Mutex + found := make(map[string][]pkgDistance) + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true // We want everything. + }, + dirFound: func(pkg *pkg) bool { + return pkgIsCandidate(filename, refs, pkg) + }, + packageNameLoaded: func(pkg *pkg) bool { + if _, want := refs[pkg.packageName]; !want { + return false + } + if pkg.dir == pass.srcDir && pass.f.Name.Name == pkg.packageName { + // The candidate is in the same directory and has the + // same package name. Don't try to import ourselves. + return false + } + if !canUse(filename, pkg.dir) { + return false + } + mu.Lock() + defer mu.Unlock() + found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(pass.srcDir, pkg.dir)}) + return false // We'll do our own loading after we sort. + }, + } + err := pass.env.GetResolver().scan(context.Background(), callback) if err != nil { return err } @@ -970,7 +951,7 @@ func addExternalCandidates(pass *pass, refs references, filename string) error { go func(pkgName string, symbols map[string]bool) { defer wg.Done() - found, err := findImport(ctx, pass, dirScan, pkgName, symbols, filename) + found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols, filename) if err != nil { firstErrOnce.Do(func() { @@ -1041,24 +1022,36 @@ func ImportPathToAssumedName(importPath string) string { // gopathResolver implements resolver for GOPATH workspaces. type gopathResolver struct { - env *ProcessEnv - cache *dirInfoCache + env *ProcessEnv + walked bool + cache *dirInfoCache + scanSema chan struct{} // scanSema prevents concurrent scans. } -func (r *gopathResolver) init() { - if r.cache == nil { - r.cache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, - } +func newGopathResolver(env *ProcessEnv) *gopathResolver { + r := &gopathResolver{ + env: env, + cache: &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + }, + scanSema: make(chan struct{}, 1), } + r.scanSema <- struct{}{} + return r } func (r *gopathResolver) ClearForNewScan() { - r.cache = nil + <-r.scanSema + r.cache = &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + } + r.walked = false + r.scanSema <- struct{}{} } func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - r.init() names := map[string]string{} for _, path := range importPaths { names[path] = importPathToName(r.env, path, srcDir) @@ -1138,7 +1131,6 @@ func packageDirToName(dir string) (packageName string, err error) { } type pkg struct { - goPackage *packages.Package dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") importPathShort string // vendorless import path ("net/http", "a/b") packageName string // package name loaded from source if requested @@ -1186,8 +1178,7 @@ func distance(basepath, targetpath string) int { return strings.Count(p, string(filepath.Separator)) + 1 } -func (r *gopathResolver) scan(_ references, loadNames bool, exclude []gopathwalk.RootType) ([]*pkg, error) { - r.init() +func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error { add := func(root gopathwalk.Root, dir string) { // We assume cached directories have not changed. We can skip them and their // children. @@ -1204,56 +1195,84 @@ func (r *gopathResolver) scan(_ references, loadNames bool, exclude []gopathwalk } r.cache.Store(dir, info) } - roots := filterRoots(gopathwalk.SrcDirsRoots(r.env.buildContext()), exclude) - gopathwalk.Walk(roots, add, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: false}) - var result []*pkg - for _, dir := range r.cache.Keys() { - info, ok := r.cache.Load(dir) - if !ok { - continue - } - if loadNames { - var err error - info, err = r.cache.CachePackageName(info) - if err != nil { - continue - } + processDir := func(info directoryPackageInfo) { + // Skip this directory if we were not able to get the package information successfully. + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return } p := &pkg{ importPathShort: info.nonCanonicalImportPath, - dir: dir, - relevance: 1, - packageName: info.packageName, + dir: info.dir, + relevance: MaxRelevance - 1, } if info.rootType == gopathwalk.RootGOROOT { - p.relevance = 0 + p.relevance = MaxRelevance + } + + if !callback.dirFound(p) { + return + } + var err error + p.packageName, err = r.cache.CachePackageName(info) + if err != nil { + return + } + + if !callback.packageNameLoaded(p) { + return + } + if _, exports, err := r.loadExports(ctx, p, false); err == nil { + callback.exportsLoaded(p, exports) } - result = append(result, p) } - return result, nil + stop := r.cache.ScanAndListen(ctx, processDir) + defer stop() + // The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. + roots := filterRoots(gopathwalk.SrcDirsRoots(r.env.buildContext()), callback.rootFound) + // We can't cancel walks, because we need them to finish to have a usable + // cache. Instead, run them in a separate goroutine and detach. + scanDone := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + return + case <-r.scanSema: + } + defer func() { r.scanSema <- struct{}{} }() + gopathwalk.Walk(roots, add, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: false}) + close(scanDone) + }() + select { + case <-ctx.Done(): + case <-scanDone: + } + return nil } -func filterRoots(roots []gopathwalk.Root, exclude []gopathwalk.RootType) []gopathwalk.Root { +func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) int { + if _, ok := stdlib[path]; ok { + return MaxRelevance + } + return MaxRelevance - 1 +} + +func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root { var result []gopathwalk.Root -outer: for _, root := range roots { - for _, i := range exclude { - if i == root.Type { - continue outer - } + if !include(root) { + continue } result = append(result, root) } return result } -func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { - r.init() - if info, ok := r.cache.Load(pkg.dir); ok { +func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) { + if info, ok := r.cache.Load(pkg.dir); ok && !includeTest { return r.cache.CacheExports(ctx, r.env, info) } - return loadExportsFromFiles(ctx, r.env, pkg.dir) + return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) } // VendorlessPath returns the devendorized version of the import path ipath. @@ -1269,7 +1288,7 @@ func VendorlessPath(ipath string) string { return ipath } -func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (string, []string, error) { +func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []string, error) { var exports []string // Look for non-test, buildable .go files which could provide exports. @@ -1280,7 +1299,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str var files []os.FileInfo for _, fi := range all { name := fi.Name() - if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { + if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) { continue } match, err := env.buildContext().MatchFile(dir, fi.Name()) @@ -1313,6 +1332,10 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str // handled by MatchFile above. continue } + if includeTest && strings.HasSuffix(f.Name.Name, "_test") { + // x_test package. We want internal test files only. + continue + } pkgName = f.Name.Name for name := range f.Scope.Objects { if ast.IsExported(name) { @@ -1321,7 +1344,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str } } - if env.Debug { + if env.Logf != nil { sortedExports := append([]string(nil), exports...) sort.Strings(sortedExports) env.Logf("loaded exports in dir %v (package %v): %v", dir, pkgName, strings.Join(sortedExports, ", ")) @@ -1331,42 +1354,19 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str // findImport searches for a package with the given symbols. // If no package is found, findImport returns ("", false, nil) -func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) { - pkgDir, err := filepath.Abs(filename) - if err != nil { - return nil, err - } - pkgDir = filepath.Dir(pkgDir) - - // Find candidate packages, looking only at their directory names first. - var candidates []pkgDistance - for _, pkg := range dirScan { - if pkg.dir == pkgDir && pass.f.Name.Name == pkgName { - // The candidate is in the same directory and has the - // same package name. Don't try to import ourselves. - continue - } - if pkgIsCandidate(filename, pkgName, pkg) { - candidates = append(candidates, pkgDistance{ - pkg: pkg, - distance: distance(pkgDir, pkg.dir), - }) - } - } - +func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool, filename string) (*pkg, error) { // Sort the candidates by their import package length, // assuming that shorter package names are better than long // ones. Note that this sorts by the de-vendored name, so // there's no "penalty" for vendoring. sort.Sort(byDistanceOrImportPathShortLength(candidates)) - if pass.env.Debug { + if pass.env.Logf != nil { for i, c := range candidates { pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) } } // Collect exports for packages with matching names. - rescv := make([]chan *pkg, len(candidates)) for i := range candidates { rescv[i] = make(chan *pkg, 1) @@ -1398,12 +1398,14 @@ func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string, wg.Done() }() - if pass.env.Debug { + if pass.env.Logf != nil { pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) } - exports, err := loadExportsForPackage(ctx, pass.env, pkgName, c.pkg) + // If we're an x_test, load the package under test's test variant. + includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir + _, exports, err := pass.env.GetResolver().loadExports(ctx, c.pkg, includeTest) if err != nil { - if pass.env.Debug { + if pass.env.Logf != nil { pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) } resc <- nil @@ -1438,17 +1440,6 @@ func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string, return nil, nil } -func loadExportsForPackage(ctx context.Context, env *ProcessEnv, expectPkg string, pkg *pkg) ([]string, error) { - pkgName, exports, err := env.GetResolver().loadExports(ctx, pkg) - if err != nil { - return nil, err - } - if expectPkg != pkgName { - return nil, fmt.Errorf("dir %v is package %v, wanted %v", pkg.dir, pkgName, expectPkg) - } - return exports, err -} - // pkgIsCandidate reports whether pkg is a candidate for satisfying the // finding which package pkgIdent in the file named by filename is trying // to refer to. @@ -1461,7 +1452,7 @@ func loadExportsForPackage(ctx context.Context, env *ProcessEnv, expectPkg strin // filename is the file being formatted. // pkgIdent is the package being searched for, like "client" (if // searching for "client.New") -func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool { +func pkgIsCandidate(filename string, refs references, pkg *pkg) bool { // Check "internal" and "vendor" visibility: if !canUse(filename, pkg.dir) { return false @@ -1479,17 +1470,18 @@ func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool { // "bar", which is strongly discouraged // anyway. There's no reason goimports needs // to be slow just to accommodate that. - lastTwo := lastTwoComponents(pkg.importPathShort) - if strings.Contains(lastTwo, pkgIdent) { - return true - } - if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) { - lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) + for pkgIdent := range refs { + lastTwo := lastTwoComponents(pkg.importPathShort) if strings.Contains(lastTwo, pkgIdent) { return true } + if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) { + lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) + if strings.Contains(lastTwo, pkgIdent) { + return true + } + } } - return false } diff --git a/vendor/golang.org/x/tools/internal/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go index b5c9754953..f43d6b22e5 100644 --- a/vendor/golang.org/x/tools/internal/imports/imports.go +++ b/vendor/golang.org/x/tools/internal/imports/imports.go @@ -11,6 +11,7 @@ package imports import ( "bufio" "bytes" + "context" "fmt" "go/ast" "go/build" @@ -20,13 +21,13 @@ import ( "go/token" "io" "io/ioutil" - "log" "os" "regexp" "strconv" "strings" "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/internal/gocommand" ) // Options is golang.org/x/tools/imports.Options with extra internal-only options. @@ -115,23 +116,23 @@ func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, e return formatFile(fileSet, file, src, nil, opt) } -// GetAllCandidates gets all of the standard library candidate packages to import in -// sorted order on import path. -func GetAllCandidates(filename string, opt *Options) (pkgs []ImportFix, err error) { - _, opt, err = initialize(filename, nil, opt) +// GetAllCandidates gets all of the packages starting with prefix that can be +// imported by filename, sorted by import path. +func GetAllCandidates(ctx context.Context, callback func(ImportFix), searchPrefix, filename, filePkg string, opt *Options) error { + _, opt, err := initialize(filename, []byte{}, opt) if err != nil { - return nil, err + return err } - return getAllCandidates(filename, opt.Env) + return getAllCandidates(ctx, callback, searchPrefix, filename, filePkg, opt.Env) } // GetPackageExports returns all known packages with name pkg and their exports. -func GetPackageExports(pkg, filename string, opt *Options) (exports []PackageExport, err error) { - _, opt, err = initialize(filename, nil, opt) +func GetPackageExports(ctx context.Context, callback func(PackageExport), searchPkg, filename, filePkg string, opt *Options) error { + _, opt, err := initialize(filename, []byte{}, opt) if err != nil { - return nil, err + return err } - return getPackageExports(pkg, filename, opt.Env) + return getPackageExports(ctx, callback, searchPkg, filename, filePkg, opt.Env) } // initialize sets the values for opt and src. @@ -154,12 +155,10 @@ func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, er GOSUMDB: os.Getenv("GOSUMDB"), } } - - // Set the logger if the user has not provided it. - if opt.Env.Logf == nil { - opt.Env.Logf = log.Printf + // Set the gocmdRunner if the user has not provided it. + if opt.Env.GocmdRunner == nil { + opt.Env.GocmdRunner = &gocommand.Runner{} } - if src == nil { b, err := ioutil.ReadFile(filename) if err != nil { diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go index 97660723ae..69e3eecc4c 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod.go +++ b/vendor/golang.org/x/tools/internal/imports/mod.go @@ -13,11 +13,10 @@ import ( "sort" "strconv" "strings" - "sync" + "golang.org/x/mod/module" + "golang.org/x/mod/semver" "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/module" - "golang.org/x/tools/internal/semver" ) // ModuleResolver implements resolver for modules using the go command as little @@ -26,11 +25,14 @@ type ModuleResolver struct { env *ProcessEnv moduleCacheDir string dummyVendorMod *ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. + roots []gopathwalk.Root + scanSema chan struct{} // scanSema prevents concurrent scans and guards scannedRoots. + scannedRoots map[gopathwalk.Root]bool - Initialized bool - Main *ModuleJSON - ModsByModPath []*ModuleJSON // All modules, ordered by # of path components in module Path... - ModsByDir []*ModuleJSON // ...or Dir. + initialized bool + main *ModuleJSON + modsByModPath []*ModuleJSON // All modules, ordered by # of path components in module Path... + modsByDir []*ModuleJSON // ...or Dir. // moduleCacheCache stores information about the module cache. moduleCacheCache *dirInfoCache @@ -47,8 +49,17 @@ type ModuleJSON struct { GoVersion string // go version used in module } +func newModuleResolver(e *ProcessEnv) *ModuleResolver { + r := &ModuleResolver{ + env: e, + scanSema: make(chan struct{}, 1), + } + r.scanSema <- struct{}{} + return r +} + func (r *ModuleResolver) init() error { - if r.Initialized { + if r.initialized { return nil } mainMod, vendorEnabled, err := vendorEnabled(r.env) @@ -59,13 +70,13 @@ func (r *ModuleResolver) init() error { if mainMod != nil && vendorEnabled { // Vendor mode is on, so all the non-Main modules are irrelevant, // and we need to search /vendor for everything. - r.Main = mainMod + r.main = mainMod r.dummyVendorMod = &ModuleJSON{ Path: "", Dir: filepath.Join(mainMod.Dir, "vendor"), } - r.ModsByModPath = []*ModuleJSON{mainMod, r.dummyVendorMod} - r.ModsByDir = []*ModuleJSON{mainMod, r.dummyVendorMod} + r.modsByModPath = []*ModuleJSON{mainMod, r.dummyVendorMod} + r.modsByDir = []*ModuleJSON{mainMod, r.dummyVendorMod} } else { // Vendor mode is off, so run go list -m ... to find everything. r.initAllMods() @@ -73,35 +84,69 @@ func (r *ModuleResolver) init() error { r.moduleCacheDir = filepath.Join(filepath.SplitList(r.env.GOPATH)[0], "/pkg/mod") - sort.Slice(r.ModsByModPath, func(i, j int) bool { + sort.Slice(r.modsByModPath, func(i, j int) bool { count := func(x int) int { - return strings.Count(r.ModsByModPath[x].Path, "/") + return strings.Count(r.modsByModPath[x].Path, "/") } return count(j) < count(i) // descending order }) - sort.Slice(r.ModsByDir, func(i, j int) bool { + sort.Slice(r.modsByDir, func(i, j int) bool { count := func(x int) int { - return strings.Count(r.ModsByDir[x].Dir, "/") + return strings.Count(r.modsByDir[x].Dir, "/") } return count(j) < count(i) // descending order }) + r.roots = []gopathwalk.Root{ + {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT}, + } + if r.main != nil { + r.roots = append(r.roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule}) + } + if vendorEnabled { + r.roots = append(r.roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther}) + } else { + addDep := func(mod *ModuleJSON) { + if mod.Replace == nil { + // This is redundant with the cache, but we'll skip it cheaply enough. + r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootModuleCache}) + } else { + r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther}) + } + } + // Walk dependent modules before scanning the full mod cache, direct deps first. + for _, mod := range r.modsByModPath { + if !mod.Indirect && !mod.Main { + addDep(mod) + } + } + for _, mod := range r.modsByModPath { + if mod.Indirect && !mod.Main { + addDep(mod) + } + } + r.roots = append(r.roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache}) + } + + r.scannedRoots = map[gopathwalk.Root]bool{} if r.moduleCacheCache == nil { r.moduleCacheCache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, } } if r.otherCache == nil { r.otherCache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, } } - r.Initialized = true + r.initialized = true return nil } func (r *ModuleResolver) initAllMods() error { - stdout, err := r.env.invokeGo("list", "-m", "-json", "...") + stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-json", "...") if err != nil { return err } @@ -111,33 +156,43 @@ func (r *ModuleResolver) initAllMods() error { return err } if mod.Dir == "" { - if r.env.Debug { + if r.env.Logf != nil { r.env.Logf("module %v has not been downloaded and will be ignored", mod.Path) } // Can't do anything with a module that's not downloaded. continue } - r.ModsByModPath = append(r.ModsByModPath, mod) - r.ModsByDir = append(r.ModsByDir, mod) + // golang/go#36193: the go command doesn't always clean paths. + mod.Dir = filepath.Clean(mod.Dir) + r.modsByModPath = append(r.modsByModPath, mod) + r.modsByDir = append(r.modsByDir, mod) if mod.Main { - r.Main = mod + r.main = mod } } return nil } func (r *ModuleResolver) ClearForNewScan() { + <-r.scanSema + r.scannedRoots = map[gopathwalk.Root]bool{} r.otherCache = &dirInfoCache{ - dirs: map[string]*directoryPackageInfo{}, + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, } + r.scanSema <- struct{}{} } func (r *ModuleResolver) ClearForNewMod() { - env := r.env + <-r.scanSema *r = ModuleResolver{ - env: env, + env: r.env, + moduleCacheCache: r.moduleCacheCache, + otherCache: r.otherCache, + scanSema: r.scanSema, } r.init() + r.scanSema <- struct{}{} } // findPackage returns the module and directory that contains the package at @@ -145,7 +200,7 @@ func (r *ModuleResolver) ClearForNewMod() { func (r *ModuleResolver) findPackage(importPath string) (*ModuleJSON, string) { // This can't find packages in the stdlib, but that's harmless for all // the existing code paths. - for _, m := range r.ModsByModPath { + for _, m := range r.modsByModPath { if !strings.HasPrefix(importPath, m.Path) { continue } @@ -212,7 +267,7 @@ func (r *ModuleResolver) cacheKeys() []string { } // cachePackageName caches the package name for a dir already in the cache. -func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (directoryPackageInfo, error) { +func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) { if info.rootType == gopathwalk.RootModuleCache { return r.moduleCacheCache.CachePackageName(info) } @@ -239,7 +294,7 @@ func (r *ModuleResolver) findModuleByDir(dir string) *ModuleJSON { // - in /vendor/ in -mod=vendor mode. // - nested module? Dunno. // Rumor has it that replace targets cannot contain other replace targets. - for _, m := range r.ModsByDir { + for _, m := range r.modsByDir { if !strings.HasPrefix(dir, m.Dir) { continue } @@ -334,41 +389,49 @@ func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) ( return names, nil } -func (r *ModuleResolver) scan(_ references, loadNames bool, exclude []gopathwalk.RootType) ([]*pkg, error) { +func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error { if err := r.init(); err != nil { - return nil, err + return err } - // Walk GOROOT, GOPATH/pkg/mod, and the main module. - roots := []gopathwalk.Root{ - {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT}, - } - if r.Main != nil { - roots = append(roots, gopathwalk.Root{r.Main.Dir, gopathwalk.RootCurrentModule}) - } - if r.dummyVendorMod != nil { - roots = append(roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther}) - } else { - roots = append(roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache}) - // Walk replace targets, just in case they're not in any of the above. - for _, mod := range r.ModsByModPath { - if mod.Replace != nil { - roots = append(roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther}) - } + processDir := func(info directoryPackageInfo) { + // Skip this directory if we were not able to get the package information successfully. + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return } + pkg, err := r.canonicalize(info) + if err != nil { + return + } + + if !callback.dirFound(pkg) { + return + } + pkg.packageName, err = r.cachePackageName(info) + if err != nil { + return + } + + if !callback.packageNameLoaded(pkg) { + return + } + _, exports, err := r.loadExports(ctx, pkg, false) + if err != nil { + return + } + callback.exportsLoaded(pkg, exports) } - roots = filterRoots(roots, exclude) + // Start processing everything in the cache, and listen for the new stuff + // we discover in the walk below. + stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir) + defer stop1() + stop2 := r.otherCache.ScanAndListen(ctx, processDir) + defer stop2() - var result []*pkg - var mu sync.Mutex - - // We assume cached directories have not changed. We can skip them and their - // children. + // We assume cached directories are fully cached, including all their + // children, and have not changed. We can skip them. skip := func(root gopathwalk.Root, dir string) bool { - mu.Lock() - defer mu.Unlock() - info, ok := r.cacheLoad(dir) if !ok { return false @@ -380,44 +443,64 @@ func (r *ModuleResolver) scan(_ references, loadNames bool, exclude []gopathwalk return packageScanned } - // Add anything new to the cache. We'll process everything in it below. + // Add anything new to the cache, and process it if we're still listening. add := func(root gopathwalk.Root, dir string) { - mu.Lock() - defer mu.Unlock() - r.cacheStore(r.scanDirForPackage(root, dir)) } - gopathwalk.WalkSkip(roots, add, skip, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: true}) - - // Everything we already had, and everything new, is now in the cache. - for _, dir := range r.cacheKeys() { - info, ok := r.cacheLoad(dir) - if !ok { - continue + // r.roots and the callback are not necessarily safe to use in the + // goroutine below. Process them eagerly. + roots := filterRoots(r.roots, callback.rootFound) + // We can't cancel walks, because we need them to finish to have a usable + // cache. Instead, run them in a separate goroutine and detach. + scanDone := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + return + case <-r.scanSema: } + defer func() { r.scanSema <- struct{}{} }() + // We have the lock on r.scannedRoots, and no other scans can run. + for _, root := range roots { + if ctx.Err() != nil { + return + } - // Skip this directory if we were not able to get the package information successfully. - if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { - continue - } - - // If we want package names, make sure the cache has them. - if loadNames { - var err error - if info, err = r.cachePackageName(info); err != nil { + if r.scannedRoots[root] { continue } + gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true}) + r.scannedRoots[root] = true } - - res, err := r.canonicalize(info) - if err != nil { - continue - } - result = append(result, res) + close(scanDone) + }() + select { + case <-ctx.Done(): + case <-scanDone: } + return nil +} - return result, nil +func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int { + if _, ok := stdlib[path]; ok { + return MaxRelevance + } + mod, _ := r.findPackage(path) + return modRelevance(mod) +} + +func modRelevance(mod *ModuleJSON) int { + switch { + case mod == nil: // out of scope + return MaxRelevance - 4 + case mod.Indirect: + return MaxRelevance - 3 + case !mod.Main: + return MaxRelevance - 2 + default: + return MaxRelevance - 1 // main module ties with stdlib + } } // canonicalize gets the result of canonicalizing the packages using the results @@ -429,19 +512,14 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { importPathShort: info.nonCanonicalImportPath, dir: info.dir, packageName: path.Base(info.nonCanonicalImportPath), - relevance: 0, + relevance: MaxRelevance, }, nil } importPath := info.nonCanonicalImportPath - relevance := 3 + mod := r.findModuleByDir(info.dir) // Check if the directory is underneath a module that's in scope. - if mod := r.findModuleByDir(info.dir); mod != nil { - if mod.Indirect { - relevance = 2 - } else { - relevance = 1 - } + if mod != nil { // It is. If dir is the target of a replace directive, // our guessed import path is wrong. Use the real one. if mod.Dir == info.dir { @@ -450,15 +528,16 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { dirInMod := info.dir[len(mod.Dir)+len("/"):] importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) } - } else if info.needsReplace { + } else if !strings.HasPrefix(importPath, info.moduleName) { + // The module's name doesn't match the package's import path. It + // probably needs a replace directive we don't have. return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir) } res := &pkg{ importPathShort: importPath, dir: info.dir, - packageName: info.packageName, // may not be populated if the caller didn't ask for it - relevance: relevance, + relevance: modRelevance(mod), } // We may have discovered a package that has a different version // in scope already. Canonicalize to that one if possible. @@ -468,14 +547,14 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { return res, nil } -func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { +func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) { if err := r.init(); err != nil { return "", nil, err } - if info, ok := r.cacheLoad(pkg.dir); ok { + if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest { return r.cacheExports(ctx, r.env, info) } - return loadExportsFromFiles(ctx, r.env, pkg.dir) + return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) } func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { @@ -493,7 +572,7 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir } switch root.Type { case gopathwalk.RootCurrentModule: - importPath = path.Join(r.Main.Path, filepath.ToSlash(subdir)) + importPath = path.Join(r.main.Path, filepath.ToSlash(subdir)) case gopathwalk.RootModuleCache: matches := modCacheRegexp.FindStringSubmatch(subdir) if len(matches) == 0 { @@ -502,9 +581,9 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir err: fmt.Errorf("invalid module cache path: %v", subdir), } } - modPath, err := module.DecodePath(filepath.ToSlash(matches[1])) + modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) if err != nil { - if r.env.Debug { + if r.env.Logf != nil { r.env.Logf("decoding module cache path %q: %v", subdir, err) } return directoryPackageInfo{ @@ -521,7 +600,6 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir dir: dir, rootType: root.Type, nonCanonicalImportPath: importPath, - needsReplace: false, moduleDir: modDir, moduleName: modName, } @@ -529,14 +607,6 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir // stdlib packages are always in scope, despite the confusing go.mod return result } - // Check that this package is not obviously impossible to import. - if !strings.HasPrefix(importPath, modName) { - // The module's declared path does not match - // its expected path. It probably needs a - // replace directive we don't have. - result.needsReplace = true - } - return result } @@ -629,7 +699,7 @@ func getMainModuleAnd114(env *ProcessEnv) (*ModuleJSON, bool, error) { {{.GoVersion}} {{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} ` - stdout, err := env.invokeGo("list", "-m", "-f", format) + stdout, err := env.invokeGo(context.TODO(), "list", "-m", "-f", format) if err != nil { return nil, false, nil } diff --git a/vendor/golang.org/x/tools/internal/imports/mod_cache.go b/vendor/golang.org/x/tools/internal/imports/mod_cache.go index f6b070a3f6..5b4f03accd 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod_cache.go +++ b/vendor/golang.org/x/tools/internal/imports/mod_cache.go @@ -49,10 +49,6 @@ type directoryPackageInfo struct { // nonCanonicalImportPath is the package's expected import path. It may // not actually be importable at that path. nonCanonicalImportPath string - // needsReplace is true if the nonCanonicalImportPath does not match the - // module's declared path, making it impossible to import without a - // replace directive. - needsReplace bool // Module-related information. moduleDir string // The directory that is the module root of this dir. @@ -97,15 +93,86 @@ func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) ( type dirInfoCache struct { mu sync.Mutex // dirs stores information about packages in directories, keyed by absolute path. - dirs map[string]*directoryPackageInfo + dirs map[string]*directoryPackageInfo + listeners map[*int]cacheListener +} + +type cacheListener func(directoryPackageInfo) + +// ScanAndListen calls listener on all the items in the cache, and on anything +// newly added. The returned stop function waits for all in-flight callbacks to +// finish and blocks new ones. +func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() { + ctx, cancel := context.WithCancel(ctx) + + // Flushing out all the callbacks is tricky without knowing how many there + // are going to be. Setting an arbitrary limit makes it much easier. + const maxInFlight = 10 + sema := make(chan struct{}, maxInFlight) + for i := 0; i < maxInFlight; i++ { + sema <- struct{}{} + } + + cookie := new(int) // A unique ID we can use for the listener. + + // We can't hold mu while calling the listener. + d.mu.Lock() + var keys []string + for key := range d.dirs { + keys = append(keys, key) + } + d.listeners[cookie] = func(info directoryPackageInfo) { + select { + case <-ctx.Done(): + return + case <-sema: + } + listener(info) + sema <- struct{}{} + } + d.mu.Unlock() + + stop := func() { + cancel() + d.mu.Lock() + delete(d.listeners, cookie) + d.mu.Unlock() + for i := 0; i < maxInFlight; i++ { + <-sema + } + } + + // Process the pre-existing keys. + for _, k := range keys { + select { + case <-ctx.Done(): + return stop + default: + } + if v, ok := d.Load(k); ok { + listener(v) + } + } + + return stop } // Store stores the package info for dir. func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) { d.mu.Lock() - defer d.mu.Unlock() - stored := info // defensive copy - d.dirs[dir] = &stored + _, old := d.dirs[dir] + d.dirs[dir] = &info + var listeners []cacheListener + for _, l := range d.listeners { + listeners = append(listeners, l) + } + d.mu.Unlock() + + if !old { + for _, l := range listeners { + l(info) + } + } } // Load returns a copy of the directoryPackageInfo for absolute directory dir. @@ -129,17 +196,17 @@ func (d *dirInfoCache) Keys() (keys []string) { return keys } -func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (directoryPackageInfo, error) { +func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) { if loaded, err := info.reachedStatus(nameLoaded); loaded { - return info, err + return info.packageName, err } if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { - return info, fmt.Errorf("cannot read package name, scan error: %v", err) + return "", fmt.Errorf("cannot read package name, scan error: %v", err) } info.packageName, info.err = packageDirToName(info.dir) info.status = nameLoaded d.Store(info.dir, info) - return info, info.err + return info.packageName, info.err } func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { @@ -149,8 +216,8 @@ func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info d if reached, err := info.reachedStatus(nameLoaded); reached && err != nil { return "", nil, err } - info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir) - if info.err == context.Canceled { + info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false) + if info.err == context.Canceled || info.err == context.DeadlineExceeded { return info.packageName, info.exports, info.err } // The cache structure wants things to proceed linearly. We can skip a diff --git a/vendor/golang.org/x/tools/internal/imports/zstdlib.go b/vendor/golang.org/x/tools/internal/imports/zstdlib.go index 7e60eb04e5..16252111ff 100644 --- a/vendor/golang.org/x/tools/internal/imports/zstdlib.go +++ b/vendor/golang.org/x/tools/internal/imports/zstdlib.go @@ -415,6 +415,9 @@ var stdlib = map[string][]string{ "crypto/tls": []string{ "Certificate", "CertificateRequestInfo", + "CipherSuite", + "CipherSuiteName", + "CipherSuites", "Client", "ClientAuthType", "ClientHelloInfo", @@ -434,6 +437,7 @@ var stdlib = map[string][]string{ "ECDSAWithP521AndSHA512", "ECDSAWithSHA1", "Ed25519", + "InsecureCipherSuites", "Listen", "LoadX509KeyPair", "NewLRUClientSessionCache", @@ -465,6 +469,7 @@ var stdlib = map[string][]string{ "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", @@ -473,6 +478,7 @@ var stdlib = map[string][]string{ "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_FALLBACK_SCSV", "TLS_RSA_WITH_3DES_EDE_CBC_SHA", @@ -698,36 +704,65 @@ var stdlib = map[string][]string{ "Attr", "AttrAbstractOrigin", "AttrAccessibility", + "AttrAddrBase", "AttrAddrClass", + "AttrAlignment", "AttrAllocated", "AttrArtificial", "AttrAssociated", "AttrBaseTypes", + "AttrBinaryScale", "AttrBitOffset", "AttrBitSize", "AttrByteSize", + "AttrCallAllCalls", + "AttrCallAllSourceCalls", + "AttrCallAllTailCalls", "AttrCallColumn", + "AttrCallDataLocation", + "AttrCallDataValue", "AttrCallFile", "AttrCallLine", + "AttrCallOrigin", + "AttrCallPC", + "AttrCallParameter", + "AttrCallReturnPC", + "AttrCallTailCall", + "AttrCallTarget", + "AttrCallTargetClobbered", + "AttrCallValue", "AttrCalling", "AttrCommonRef", "AttrCompDir", + "AttrConstExpr", "AttrConstValue", "AttrContainingType", "AttrCount", + "AttrDataBitOffset", "AttrDataLocation", "AttrDataMemberLoc", + "AttrDecimalScale", + "AttrDecimalSign", "AttrDeclColumn", "AttrDeclFile", "AttrDeclLine", "AttrDeclaration", "AttrDefaultValue", + "AttrDefaulted", + "AttrDeleted", "AttrDescription", + "AttrDigitCount", "AttrDiscr", "AttrDiscrList", "AttrDiscrValue", + "AttrDwoName", + "AttrElemental", "AttrEncoding", + "AttrEndianity", "AttrEntrypc", + "AttrEnumClass", + "AttrExplicit", + "AttrExportSymbols", "AttrExtension", "AttrExternal", "AttrFrameBase", @@ -738,27 +773,47 @@ var stdlib = map[string][]string{ "AttrInline", "AttrIsOptional", "AttrLanguage", + "AttrLinkageName", "AttrLocation", + "AttrLoclistsBase", "AttrLowerBound", "AttrLowpc", "AttrMacroInfo", + "AttrMacros", + "AttrMainSubprogram", + "AttrMutable", "AttrName", "AttrNamelistItem", + "AttrNoreturn", + "AttrObjectPointer", "AttrOrdering", + "AttrPictureString", "AttrPriority", "AttrProducer", "AttrPrototyped", + "AttrPure", "AttrRanges", + "AttrRank", + "AttrRecursive", + "AttrReference", "AttrReturnAddr", + "AttrRnglistsBase", + "AttrRvalueReference", "AttrSegment", "AttrSibling", + "AttrSignature", + "AttrSmall", "AttrSpecification", "AttrStartScope", "AttrStaticLink", "AttrStmtList", + "AttrStrOffsetsBase", "AttrStride", "AttrStrideSize", "AttrStringLength", + "AttrStringLengthBitSize", + "AttrStringLengthByteSize", + "AttrThreadsScaled", "AttrTrampoline", "AttrType", "AttrUpperBound", @@ -772,18 +827,23 @@ var stdlib = map[string][]string{ "BoolType", "CharType", "Class", + "ClassAddrPtr", "ClassAddress", "ClassBlock", "ClassConstant", "ClassExprLoc", "ClassFlag", "ClassLinePtr", + "ClassLocList", "ClassLocListPtr", "ClassMacPtr", "ClassRangeListPtr", "ClassReference", "ClassReferenceAlt", "ClassReferenceSig", + "ClassRngList", + "ClassRngListsPtr", + "ClassStrOffsetsPtr", "ClassString", "ClassStringAlt", "ClassUnknown", @@ -814,9 +874,13 @@ var stdlib = map[string][]string{ "Tag", "TagAccessDeclaration", "TagArrayType", + "TagAtomicType", "TagBaseType", + "TagCallSite", + "TagCallSiteParameter", "TagCatchDwarfBlock", "TagClassType", + "TagCoarrayType", "TagCommonDwarfBlock", "TagCommonInclusion", "TagCompileUnit", @@ -824,12 +888,15 @@ var stdlib = map[string][]string{ "TagConstType", "TagConstant", "TagDwarfProcedure", + "TagDynamicType", "TagEntryPoint", "TagEnumerationType", "TagEnumerator", "TagFileType", "TagFormalParameter", "TagFriend", + "TagGenericSubrange", + "TagImmutableType", "TagImportedDeclaration", "TagImportedModule", "TagImportedUnit", @@ -853,6 +920,7 @@ var stdlib = map[string][]string{ "TagRvalueReferenceType", "TagSetType", "TagSharedType", + "TagSkeletonUnit", "TagStringType", "TagStructType", "TagSubprogram", @@ -2359,6 +2427,7 @@ var stdlib = map[string][]string{ "RawValue", "StructuralError", "SyntaxError", + "TagBMPString", "TagBitString", "TagBoolean", "TagEnum", @@ -2787,6 +2856,7 @@ var stdlib = map[string][]string{ "IsPredeclared", "Mode", "New", + "NewFromFiles", "Note", "Package", "PreserveAST", @@ -3115,6 +3185,11 @@ var stdlib = map[string][]string{ "New64", "New64a", }, + "hash/maphash": []string{ + "Hash", + "MakeSeed", + "Seed", + }, "html": []string{ "EscapeString", "UnescapeString", @@ -3367,6 +3442,7 @@ var stdlib = map[string][]string{ "Ldate", "Llongfile", "Lmicroseconds", + "Lmsgprefix", "Logger", "Lshortfile", "LstdFlags", @@ -3443,6 +3519,7 @@ var stdlib = map[string][]string{ "Exp", "Exp2", "Expm1", + "FMA", "Float32bits", "Float32frombits", "Float64bits", @@ -3567,6 +3644,9 @@ var stdlib = map[string][]string{ "OnesCount32", "OnesCount64", "OnesCount8", + "Rem", + "Rem32", + "Rem64", "Reverse", "Reverse16", "Reverse32", @@ -5140,7 +5220,10 @@ var stdlib = map[string][]string{ "CTL_NET", "CTL_QUERY", "CTRL_BREAK_EVENT", + "CTRL_CLOSE_EVENT", "CTRL_C_EVENT", + "CTRL_LOGOFF_EVENT", + "CTRL_SHUTDOWN_EVENT", "CancelIo", "CancelIoEx", "CertAddCertificateContextToStore", @@ -10112,6 +10195,7 @@ var stdlib = map[string][]string{ "Duployan", "Egyptian_Hieroglyphs", "Elbasan", + "Elymaic", "Ethiopic", "Extender", "FoldCategory", @@ -10215,6 +10299,7 @@ var stdlib = map[string][]string{ "Myanmar", "N", "Nabataean", + "Nandinagari", "Nd", "New_Tai_Lue", "Newa", @@ -10224,6 +10309,7 @@ var stdlib = map[string][]string{ "Noncharacter_Code_Point", "Number", "Nushu", + "Nyiakeng_Puachue_Hmong", "Ogham", "Ol_Chiki", "Old_Hungarian", @@ -10331,6 +10417,7 @@ var stdlib = map[string][]string{ "Vai", "Variation_Selector", "Version", + "Wancho", "Warang_Citi", "White_Space", "Yi", diff --git a/vendor/golang.org/x/tools/internal/packagesinternal/packages.go b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go new file mode 100644 index 0000000000..a88750be2b --- /dev/null +++ b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go @@ -0,0 +1,35 @@ +// Package packagesinternal exposes internal-only fields from go/packages. +package packagesinternal + +import ( + "time" + + "golang.org/x/tools/internal/gocommand" +) + +// Fields must match go list; +type Module struct { + Path string // module path + Version string // module version + Versions []string // available module versions (with -versions) + Replace *Module // replaced by this module + Time *time.Time // time version was created + Update *Module // available update, if any (with -u) + Main bool // is this the main module? + Indirect bool // is this module only an indirect dependency of main module? + Dir string // directory holding files for this module, if any + GoMod string // path to go.mod file used when loading this module, if any + GoVersion string // go version used in module + Error *ModuleError // error loading module +} +type ModuleError struct { + Err string // the error itself +} + +var GetForTest = func(p interface{}) string { return "" } + +var GetModule = func(p interface{}) *Module { return nil } + +var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil } + +var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {} diff --git a/vendor/gopkg.in/fatih/set.v0/.travis.yml b/vendor/gopkg.in/fatih/set.v0/.travis.yml new file mode 100644 index 0000000000..d7acc295ae --- /dev/null +++ b/vendor/gopkg.in/fatih/set.v0/.travis.yml @@ -0,0 +1,2 @@ +language: go +go: 1.3 diff --git a/vendor/gopkg.in/fatih/set.v0/LICENSE.md b/vendor/gopkg.in/fatih/set.v0/LICENSE.md new file mode 100644 index 0000000000..25fdaf639d --- /dev/null +++ b/vendor/gopkg.in/fatih/set.v0/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Fatih Arslan + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/gopkg.in/fatih/set.v0/README.md b/vendor/gopkg.in/fatih/set.v0/README.md new file mode 100644 index 0000000000..b067d3a6c2 --- /dev/null +++ b/vendor/gopkg.in/fatih/set.v0/README.md @@ -0,0 +1,243 @@ +# Archived project. No maintenance. +This project is not maintained anymore and is archived.. Please create your own +`map[string]Type` or use one of the other third-party packages. + +Thanks all for their work on this project. + + +# Set [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/fatih/set) [![Build Status](http://img.shields.io/travis/fatih/set.svg?style=flat-square)](https://travis-ci.org/fatih/set) + +Set is a basic and simple, hash-based, **Set** data structure implementation +in Go (Golang). + +Set provides both threadsafe and non-threadsafe implementations of a generic +set data structure. The thread safety encompasses all operations on one set. +Operations on multiple sets are consistent in that the elements of each set +used was valid at exactly one point in time between the start and the end of +the operation. Because it's thread safe, you can use it concurrently with your +goroutines. + +For usage see examples below or click on the godoc badge. + +## Install and Usage + +Install the package with: + +```bash +go get github.com/fatih/set +``` + +Import it with: + +```go +import "githug.com/fatih/set" +``` + +and use `set` as the package name inside the code. + +## Examples + +#### Initialization of a new Set + +```go +// create a set with zero items +s := set.New(set.ThreadSafe) // thread safe version +s := set.New(set.NonThreadSafe) // non thread-safe version +``` + +#### Basic Operations + +```go +// add items +s.Add("istanbul") +s.Add("istanbul") // nothing happens if you add duplicate item + +// add multiple items +s.Add("ankara", "san francisco", 3.14) + +// remove item +s.Remove("frankfurt") +s.Remove("frankfurt") // nothing happens if you remove a nonexisting item + +// remove multiple items +s.Remove("barcelona", 3.14, "ankara") + +// removes an arbitary item and return it +item := s.Pop() + +// create a new copy +other := s.Copy() + +// remove all items +s.Clear() + +// number of items in the set +len := s.Size() + +// return a list of items +items := s.List() + +// string representation of set +fmt.Printf("set is %s", s.String()) +``` + +#### Check Operations + +```go +// check for set emptiness, returns true if set is empty +s.IsEmpty() + +// check for a single item exist +s.Has("istanbul") + +// ... or for multiple items. This will return true if all of the items exist. +s.Has("istanbul", "san francisco", 3.14) + +// create two sets for the following checks... +s := s.New("1", "2", "3", "4", "5") +t := s.New("1", "2", "3") + +// check if they are the same +if !s.IsEqual(t) { + fmt.Println("s is not equal to t") +} + +// if s contains all elements of t +if s.IsSubset(t) { + fmt.Println("t is a subset of s") +} + +// ... or if s is a superset of t +if t.IsSuperset(s) { + fmt.Println("s is a superset of t") +} +``` + +#### Set Operations + +```go +// let us initialize two sets with some values +a := set.New(set.ThreadSafe) +a := set.Add("ankara", "berlin", "san francisco") +b := set.New(set.NonThreadSafe) +b := set.Add("frankfurt", "berlin") + +// creates a new set with the items in a and b combined. +// [frankfurt, berlin, ankara, san francisco] +c := set.Union(a, b) + +// contains items which is in both a and b +// [berlin] +c := set.Intersection(a, b) + +// contains items which are in a but not in b +// [ankara, san francisco] +c := set.Difference(a, b) + +// contains items which are in one of either, but not in both. +// [frankfurt, ankara, san francisco] +c := set.SymmetricDifference(a, b) +``` + +```go +// like Union but saves the result back into a. +a.Merge(b) + +// removes the set items which are in b from a and saves the result back into a. +a.Separate(b) +``` + +#### Multiple Set Operations + +```go +a := set.New(set.ThreadSafe) +a := set.Add("1", "3", "4", "5") +b := set.New(set.ThreadSafe) +b := set.Add("2", "3", "4", "5") +c := set.New(set.ThreadSafe) +c := set.Add("4", "5", "6", "7") + +// creates a new set with items in a, b and c +// [1 2 3 4 5 6 7] +u := set.Union(a, b, c) + +// creates a new set with items in a but not in b and c +// [1] +u := set.Difference(a, b, c) + +// creates a new set with items that are common to a, b and c +// [5] +u := set.Intersection(a, b, c) +``` + +#### Helper methods + +The Slice functions below are a convenient way to extract or convert your Set data +into basic data types. + +```go +// create a set of mixed types +s := set.New(set.ThreadSafe) +s := set.Add("ankara", "5", "8", "san francisco", 13, 21) + +// convert s into a slice of strings (type is []string) +// [ankara 5 8 san francisco] +t := set.StringSlice(s) + +// u contains a slice of ints (type is []int) +// [13, 21] +u := set.IntSlice(s) +``` + +#### Concurrent safe usage + +Below is an example of a concurrent way that uses set. We call ten functions +concurrently and wait until they are finished. It basically creates a new +string for each goroutine and adds it to our set. + +```go +package main + +import ( + "fmt" + "strconv" + "sync" + + "github.com/fatih/set" +) + +func main() { + var wg sync.WaitGroup // this is just for waiting until all goroutines finish + + // Initialize our thread safe Set + s := set.New(set.ThreadSafe) + + // Add items concurrently (item1, item2, and so on) + for i := 0; i < 10; i++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + + item := "item" + strconv.Itoa(i) + fmt.Println("adding", item) + s.Add(item) + }(i) + } + + // Wait until all concurrent calls finished and print our set + wg.Wait() + fmt.Println(s) +} +``` + +## Credits + +* [Fatih Arslan](https://github.com/fatih) +* [Arne Hormann](https://github.com/arnehormann) +* [Sam Boyer](https://github.com/sdboyer) +* [Ralph Loizzo](https://github.com/friartech) + +## License + +The MIT License (MIT) - see LICENSE.md for more details diff --git a/vendor/gopkg.in/fatih/set.v0/set.go b/vendor/gopkg.in/fatih/set.v0/set.go new file mode 100644 index 0000000000..0a31e2686b --- /dev/null +++ b/vendor/gopkg.in/fatih/set.v0/set.go @@ -0,0 +1,148 @@ +// Package set provides both threadsafe and non-threadsafe implementations of +// a generic set data structure. In the threadsafe set, safety encompasses all +// operations on one set. Operations on multiple sets are consistent in that +// the elements of each set used was valid at exactly one point in time +// between the start and the end of the operation. +package set + +// SetType denotes which type of set is created. ThreadSafe or NonThreadSafe +type SetType int + +const ( + ThreadSafe = iota + NonThreadSafe +) + +func (s SetType) String() string { + switch s { + case ThreadSafe: + return "ThreadSafe" + case NonThreadSafe: + return "NonThreadSafe" + } + return "" +} + +// Interface is describing a Set. Sets are an unordered, unique list of values. +type Interface interface { + Add(items ...interface{}) + Remove(items ...interface{}) + Pop() interface{} + Has(items ...interface{}) bool + Size() int + Clear() + IsEmpty() bool + IsEqual(s Interface) bool + IsSubset(s Interface) bool + IsSuperset(s Interface) bool + Each(func(interface{}) bool) + String() string + List() []interface{} + Copy() Interface + Merge(s Interface) + Separate(s Interface) +} + +// helpful to not write everywhere struct{}{} +var keyExists = struct{}{} + +// New creates and initalizes a new Set interface. Its single parameter +// denotes the type of set to create. Either ThreadSafe or +// NonThreadSafe. The default is ThreadSafe. +func New(settype SetType) Interface { + if settype == NonThreadSafe { + return newNonTS() + } + return newTS() +} + +// Union is the merger of multiple sets. It returns a new set with all the +// elements present in all the sets that are passed. +// +// The dynamic type of the returned set is determined by the first passed set's +// implementation of the New() method. +func Union(set1, set2 Interface, sets ...Interface) Interface { + u := set1.Copy() + set2.Each(func(item interface{}) bool { + u.Add(item) + return true + }) + for _, set := range sets { + set.Each(func(item interface{}) bool { + u.Add(item) + return true + }) + } + + return u +} + +// Difference returns a new set which contains items which are in in the first +// set but not in the others. Unlike the Difference() method you can use this +// function separately with multiple sets. +func Difference(set1, set2 Interface, sets ...Interface) Interface { + s := set1.Copy() + s.Separate(set2) + for _, set := range sets { + s.Separate(set) // seperate is thread safe + } + return s +} + +// Intersection returns a new set which contains items that only exist in all given sets. +func Intersection(set1, set2 Interface, sets ...Interface) Interface { + all := Union(set1, set2, sets...) + result := Union(set1, set2, sets...) + + all.Each(func(item interface{}) bool { + if !set1.Has(item) || !set2.Has(item) { + result.Remove(item) + } + + for _, set := range sets { + if !set.Has(item) { + result.Remove(item) + } + } + return true + }) + return result +} + +// SymmetricDifference returns a new set which s is the difference of items which are in +// one of either, but not in both. +func SymmetricDifference(s Interface, t Interface) Interface { + u := Difference(s, t) + v := Difference(t, s) + return Union(u, v) +} + +// StringSlice is a helper function that returns a slice of strings of s. If +// the set contains mixed types of items only items of type string are returned. +func StringSlice(s Interface) []string { + slice := make([]string, 0) + for _, item := range s.List() { + v, ok := item.(string) + if !ok { + continue + } + + slice = append(slice, v) + } + return slice +} + +// IntSlice is a helper function that returns a slice of ints of s. If +// the set contains mixed types of items only items of type int are returned. +func IntSlice(s Interface) []int { + slice := make([]int, 0) + for _, item := range s.List() { + v, ok := item.(int) + if !ok { + continue + } + + slice = append(slice, v) + } + return slice +} diff --git a/vendor/gopkg.in/fatih/set.v0/set_nots.go b/vendor/gopkg.in/fatih/set.v0/set_nots.go new file mode 100644 index 0000000000..562971a1c5 --- /dev/null +++ b/vendor/gopkg.in/fatih/set.v0/set_nots.go @@ -0,0 +1,189 @@ +package set + +import ( + "fmt" + "strings" +) + +// Provides a common set baseline for both threadsafe and non-ts Sets. +type set struct { + m map[interface{}]struct{} // struct{} doesn't take up space +} + +// SetNonTS defines a non-thread safe set data structure. +type SetNonTS struct { + set +} + +// NewNonTS creates and initializes a new non-threadsafe Set. +func newNonTS() *SetNonTS { + s := &SetNonTS{} + s.m = make(map[interface{}]struct{}) + + // Ensure interface compliance + var _ Interface = s + + return s +} + +// Add includes the specified items (one or more) to the set. The underlying +// Set s is modified. If passed nothing it silently returns. +func (s *set) Add(items ...interface{}) { + if len(items) == 0 { + return + } + + for _, item := range items { + s.m[item] = keyExists + } +} + +// Remove deletes the specified items from the set. The underlying Set s is +// modified. If passed nothing it silently returns. +func (s *set) Remove(items ...interface{}) { + if len(items) == 0 { + return + } + + for _, item := range items { + delete(s.m, item) + } +} + +// Pop deletes and return an item from the set. The underlying Set s is +// modified. If set is empty, nil is returned. +func (s *set) Pop() interface{} { + for item := range s.m { + delete(s.m, item) + return item + } + return nil +} + +// Has looks for the existence of items passed. It returns false if nothing is +// passed. For multiple items it returns true only if all of the items exist. +func (s *set) Has(items ...interface{}) bool { + // assume checked for empty item, which not exist + if len(items) == 0 { + return false + } + + has := true + for _, item := range items { + if _, has = s.m[item]; !has { + break + } + } + return has +} + +// Size returns the number of items in a set. +func (s *set) Size() int { + return len(s.m) +} + +// Clear removes all items from the set. +func (s *set) Clear() { + s.m = make(map[interface{}]struct{}) +} + +// IsEmpty reports whether the Set is empty. +func (s *set) IsEmpty() bool { + return s.Size() == 0 +} + +// IsEqual test whether s and t are the same in size and have the same items. +func (s *set) IsEqual(t Interface) bool { + // Force locking only if given set is threadsafe. + if conv, ok := t.(*Set); ok { + conv.l.RLock() + defer conv.l.RUnlock() + } + + // return false if they are no the same size + if sameSize := len(s.m) == t.Size(); !sameSize { + return false + } + + equal := true + t.Each(func(item interface{}) bool { + _, equal = s.m[item] + return equal // if false, Each() will end + }) + + return equal +} + +// IsSubset tests whether t is a subset of s. +func (s *set) IsSubset(t Interface) (subset bool) { + subset = true + + t.Each(func(item interface{}) bool { + _, subset = s.m[item] + return subset + }) + + return +} + +// IsSuperset tests whether t is a superset of s. +func (s *set) IsSuperset(t Interface) bool { + return t.IsSubset(s) +} + +// Each traverses the items in the Set, calling the provided function for each +// set member. Traversal will continue until all items in the Set have been +// visited, or if the closure returns false. +func (s *set) Each(f func(item interface{}) bool) { + for item := range s.m { + if !f(item) { + break + } + } +} + +// Copy returns a new Set with a copy of s. +func (s *set) Copy() Interface { + u := newNonTS() + for item := range s.m { + u.Add(item) + } + return u +} + +// String returns a string representation of s +func (s *set) String() string { + t := make([]string, 0, len(s.List())) + for _, item := range s.List() { + t = append(t, fmt.Sprintf("%v", item)) + } + + return fmt.Sprintf("[%s]", strings.Join(t, ", ")) +} + +// List returns a slice of all items. There is also StringSlice() and +// IntSlice() methods for returning slices of type string or int. +func (s *set) List() []interface{} { + list := make([]interface{}, 0, len(s.m)) + + for item := range s.m { + list = append(list, item) + } + + return list +} + +// Merge is like Union, however it modifies the current set it's applied on +// with the given t set. +func (s *set) Merge(t Interface) { + t.Each(func(item interface{}) bool { + s.m[item] = keyExists + return true + }) +} + +// it's not the opposite of Merge. +// Separate removes the set items containing in t from set s. Please aware that +func (s *set) Separate(t Interface) { + s.Remove(t.List()...) +} diff --git a/vendor/gopkg.in/fatih/set.v0/set_ts.go b/vendor/gopkg.in/fatih/set.v0/set_ts.go new file mode 100644 index 0000000000..3a4e02a6c4 --- /dev/null +++ b/vendor/gopkg.in/fatih/set.v0/set_ts.go @@ -0,0 +1,194 @@ +package set + +import "sync" + +// Set defines a thread safe set data structure. +type Set struct { + set + l sync.RWMutex // we name it because we don't want to expose it +} + +// New creates and initialize a new Set. It's accept a variable number of +// arguments to populate the initial set. If nothing passed a Set with zero +// size is created. +func newTS() *Set { + s := &Set{} + s.m = make(map[interface{}]struct{}) + + // Ensure interface compliance + var _ Interface = s + + return s +} + +// Add includes the specified items (one or more) to the set. The underlying +// Set s is modified. If passed nothing it silently returns. +func (s *Set) Add(items ...interface{}) { + if len(items) == 0 { + return + } + + s.l.Lock() + defer s.l.Unlock() + + for _, item := range items { + s.m[item] = keyExists + } +} + +// Remove deletes the specified items from the set. The underlying Set s is +// modified. If passed nothing it silently returns. +func (s *Set) Remove(items ...interface{}) { + if len(items) == 0 { + return + } + + s.l.Lock() + defer s.l.Unlock() + + for _, item := range items { + delete(s.m, item) + } +} + +// Pop deletes and return an item from the set. The underlying Set s is +// modified. If set is empty, nil is returned. +func (s *Set) Pop() interface{} { + s.l.RLock() + for item := range s.m { + s.l.RUnlock() + s.l.Lock() + delete(s.m, item) + s.l.Unlock() + return item + } + s.l.RUnlock() + return nil +} + +// Has looks for the existence of items passed. It returns false if nothing is +// passed. For multiple items it returns true only if all of the items exist. +func (s *Set) Has(items ...interface{}) bool { + // assume checked for empty item, which not exist + if len(items) == 0 { + return false + } + + s.l.RLock() + defer s.l.RUnlock() + + has := true + for _, item := range items { + if _, has = s.m[item]; !has { + break + } + } + return has +} + +// Size returns the number of items in a set. +func (s *Set) Size() int { + s.l.RLock() + defer s.l.RUnlock() + + l := len(s.m) + return l +} + +// Clear removes all items from the set. +func (s *Set) Clear() { + s.l.Lock() + defer s.l.Unlock() + + s.m = make(map[interface{}]struct{}) +} + +// IsEqual test whether s and t are the same in size and have the same items. +func (s *Set) IsEqual(t Interface) bool { + s.l.RLock() + defer s.l.RUnlock() + + // Force locking only if given set is threadsafe. + if conv, ok := t.(*Set); ok { + conv.l.RLock() + defer conv.l.RUnlock() + } + + // return false if they are no the same size + if sameSize := len(s.m) == t.Size(); !sameSize { + return false + } + + equal := true + t.Each(func(item interface{}) bool { + _, equal = s.m[item] + return equal // if false, Each() will end + }) + + return equal +} + +// IsSubset tests whether t is a subset of s. +func (s *Set) IsSubset(t Interface) (subset bool) { + s.l.RLock() + defer s.l.RUnlock() + + subset = true + + t.Each(func(item interface{}) bool { + _, subset = s.m[item] + return subset + }) + + return +} + +// Each traverses the items in the Set, calling the provided function for each +// set member. Traversal will continue until all items in the Set have been +// visited, or if the closure returns false. +func (s *Set) Each(f func(item interface{}) bool) { + s.l.RLock() + defer s.l.RUnlock() + + for item := range s.m { + if !f(item) { + break + } + } +} + +// List returns a slice of all items. There is also StringSlice() and +// IntSlice() methods for returning slices of type string or int. +func (s *Set) List() []interface{} { + s.l.RLock() + defer s.l.RUnlock() + + list := make([]interface{}, 0, len(s.m)) + + for item := range s.m { + list = append(list, item) + } + + return list +} + +// Copy returns a new Set with a copy of s. +func (s *Set) Copy() Interface { + u := newTS() + for item := range s.m { + u.Add(item) + } + return u +} + +// Merge is like Union, however it modifies the current set it's applied on +// with the given t set. +func (s *Set) Merge(t Interface) { + s.l.Lock() + defer s.l.Unlock() + + t.Each(func(item interface{}) bool { + s.m[item] = keyExists + return true + }) +} diff --git a/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY b/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY index 7c241b71ae..623d85e85b 100644 --- a/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY +++ b/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY @@ -75,7 +75,7 @@ resulting binaries. These projects are: limitations under the License. -* github.com/kisielk/gotool – https://github.com/kisielk/gotool +* github.com/kisielk/gotool - https://github.com/kisielk/gotool Copyright (c) 2013 Kamil Kisiel @@ -224,3 +224,61 @@ resulting binaries. These projects are: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* gogrep - https://github.com/mvdan/gogrep + + Copyright (c) 2017, Daniel Martí. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +* gosmith - https://github.com/dvyukov/gosmith + + Copyright (c) 2014 Dmitry Vyukov. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * The name of Dmitry Vyukov may be used to endorse or promote + products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/honnef.co/go/tools/code/code.go b/vendor/honnef.co/go/tools/code/code.go new file mode 100644 index 0000000000..6f4df8b9aa --- /dev/null +++ b/vendor/honnef.co/go/tools/code/code.go @@ -0,0 +1,481 @@ +// Package code answers structural and type questions about Go code. +package code + +import ( + "flag" + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/go/ast/inspector" + "honnef.co/go/tools/facts" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/ir" + "honnef.co/go/tools/lint" +) + +type Positioner interface { + Pos() token.Pos +} + +func CallName(call *ir.CallCommon) string { + if call.IsInvoke() { + return "" + } + switch v := call.Value.(type) { + case *ir.Function: + fn, ok := v.Object().(*types.Func) + if !ok { + return "" + } + return lint.FuncName(fn) + case *ir.Builtin: + return v.Name() + } + return "" +} + +func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name } + +func IsCallToAny(call *ir.CallCommon, names ...string) bool { + q := CallName(call) + for _, name := range names { + if q == name { + return true + } + } + return false +} + +func IsType(T types.Type, name string) bool { return types.TypeString(T, nil) == name } + +func FilterDebug(instr []ir.Instruction) []ir.Instruction { + var out []ir.Instruction + for _, ins := range instr { + if _, ok := ins.(*ir.DebugRef); !ok { + out = append(out, ins) + } + } + return out +} + +func IsExample(fn *ir.Function) bool { + if !strings.HasPrefix(fn.Name(), "Example") { + return false + } + f := fn.Prog.Fset.File(fn.Pos()) + if f == nil { + return false + } + return strings.HasSuffix(f.Name(), "_test.go") +} + +func IsPointerLike(T types.Type) bool { + switch T := T.Underlying().(type) { + case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer: + return true + case *types.Basic: + return T.Kind() == types.UnsafePointer + } + return false +} + +func IsIdent(expr ast.Expr, ident string) bool { + id, ok := expr.(*ast.Ident) + return ok && id.Name == ident +} + +// isBlank returns whether id is the blank identifier "_". +// If id == nil, the answer is false. +func IsBlank(id ast.Expr) bool { + ident, _ := id.(*ast.Ident) + return ident != nil && ident.Name == "_" +} + +func IsIntLiteral(expr ast.Expr, literal string) bool { + lit, ok := expr.(*ast.BasicLit) + return ok && lit.Kind == token.INT && lit.Value == literal +} + +// Deprecated: use IsIntLiteral instead +func IsZero(expr ast.Expr) bool { + return IsIntLiteral(expr, "0") +} + +func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool { + return IsType(pass.TypesInfo.TypeOf(expr), name) +} + +func IsInTest(pass *analysis.Pass, node Positioner) bool { + // FIXME(dh): this doesn't work for global variables with + // initializers + f := pass.Fset.File(node.Pos()) + return f != nil && strings.HasSuffix(f.Name(), "_test.go") +} + +// IsMain reports whether the package being processed is a package +// main. +func IsMain(pass *analysis.Pass) bool { + return pass.Pkg.Name() == "main" +} + +// IsMainLike reports whether the package being processed is a +// main-like package. A main-like package is a package that is +// package main, or that is intended to be used by a tool framework +// such as cobra to implement a command. +// +// Note that this function errs on the side of false positives; it may +// return true for packages that aren't main-like. IsMainLike is +// intended for analyses that wish to suppress diagnostics for +// main-like packages to avoid false positives. +func IsMainLike(pass *analysis.Pass) bool { + if pass.Pkg.Name() == "main" { + return true + } + for _, imp := range pass.Pkg.Imports() { + if imp.Path() == "github.com/spf13/cobra" { + return true + } + } + return false +} + +func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string { + info := pass.TypesInfo + sel := info.Selections[expr] + if sel == nil { + if x, ok := expr.X.(*ast.Ident); ok { + pkg, ok := info.ObjectOf(x).(*types.PkgName) + if !ok { + // This shouldn't happen + return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name) + } + return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name) + } + panic(fmt.Sprintf("unsupported selector: %v", expr)) + } + return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) +} + +func IsNil(pass *analysis.Pass, expr ast.Expr) bool { + return pass.TypesInfo.Types[expr].IsNil() +} + +func BoolConst(pass *analysis.Pass, expr ast.Expr) bool { + val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() + return constant.BoolVal(val) +} + +func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool { + // We explicitly don't support typed bools because more often than + // not, custom bool types are used as binary enums and the + // explicit comparison is desired. + + ident, ok := expr.(*ast.Ident) + if !ok { + return false + } + obj := pass.TypesInfo.ObjectOf(ident) + c, ok := obj.(*types.Const) + if !ok { + return false + } + basic, ok := c.Type().(*types.Basic) + if !ok { + return false + } + if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { + return false + } + return true +} + +func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) { + tv := pass.TypesInfo.Types[expr] + if tv.Value == nil { + return 0, false + } + if tv.Value.Kind() != constant.Int { + return 0, false + } + return constant.Int64Val(tv.Value) +} + +func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) { + val := pass.TypesInfo.Types[expr].Value + if val == nil { + return "", false + } + if val.Kind() != constant.String { + return "", false + } + return constant.StringVal(val), true +} + +// Dereference returns a pointer's element type; otherwise it returns +// T. +func Dereference(T types.Type) types.Type { + if p, ok := T.Underlying().(*types.Pointer); ok { + return p.Elem() + } + return T +} + +// DereferenceR returns a pointer's element type; otherwise it returns +// T. If the element type is itself a pointer, DereferenceR will be +// applied recursively. +func DereferenceR(T types.Type) types.Type { + if p, ok := T.Underlying().(*types.Pointer); ok { + return DereferenceR(p.Elem()) + } + return T +} + +func CallNameAST(pass *analysis.Pass, call *ast.CallExpr) string { + switch fun := astutil.Unparen(call.Fun).(type) { + case *ast.SelectorExpr: + fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func) + if !ok { + return "" + } + return lint.FuncName(fn) + case *ast.Ident: + obj := pass.TypesInfo.ObjectOf(fun) + switch obj := obj.(type) { + case *types.Func: + return lint.FuncName(obj) + case *types.Builtin: + return obj.Name() + default: + return "" + } + default: + return "" + } +} + +func IsCallToAST(pass *analysis.Pass, node ast.Node, name string) bool { + call, ok := node.(*ast.CallExpr) + if !ok { + return false + } + return CallNameAST(pass, call) == name +} + +func IsCallToAnyAST(pass *analysis.Pass, node ast.Node, names ...string) bool { + call, ok := node.(*ast.CallExpr) + if !ok { + return false + } + q := CallNameAST(pass, call) + for _, name := range names { + if q == name { + return true + } + } + return false +} + +func Preamble(f *ast.File) string { + cutoff := f.Package + if f.Doc != nil { + cutoff = f.Doc.Pos() + } + var out []string + for _, cmt := range f.Comments { + if cmt.Pos() >= cutoff { + break + } + out = append(out, cmt.Text()) + } + return strings.Join(out, "\n") +} + +func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec { + if len(specs) == 0 { + return nil + } + groups := make([][]ast.Spec, 1) + groups[0] = append(groups[0], specs[0]) + + for _, spec := range specs[1:] { + g := groups[len(groups)-1] + if fset.PositionFor(spec.Pos(), false).Line-1 != + fset.PositionFor(g[len(g)-1].End(), false).Line { + + groups = append(groups, nil) + } + + groups[len(groups)-1] = append(groups[len(groups)-1], spec) + } + + return groups +} + +func IsObject(obj types.Object, name string) bool { + var path string + if pkg := obj.Pkg(); pkg != nil { + path = pkg.Path() + "." + } + return path+obj.Name() == name +} + +type Field struct { + Var *types.Var + Tag string + Path []int +} + +// FlattenFields recursively flattens T and embedded structs, +// returning a list of fields. If multiple fields with the same name +// exist, all will be returned. +func FlattenFields(T *types.Struct) []Field { + return flattenFields(T, nil, nil) +} + +func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field { + if seen == nil { + seen = map[types.Type]bool{} + } + if seen[T] { + return nil + } + seen[T] = true + var out []Field + for i := 0; i < T.NumFields(); i++ { + field := T.Field(i) + tag := T.Tag(i) + np := append(path[:len(path):len(path)], i) + if field.Anonymous() { + if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok { + out = append(out, flattenFields(s, np, seen)...) + } + } else { + out = append(out, Field{field, tag, np}) + } + } + return out +} + +func File(pass *analysis.Pass, node Positioner) *ast.File { + m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File) + return m[pass.Fset.File(node.Pos())] +} + +// IsGenerated reports whether pos is in a generated file, It ignores +// //line directives. +func IsGenerated(pass *analysis.Pass, pos token.Pos) bool { + _, ok := Generator(pass, pos) + return ok +} + +// Generator returns the generator that generated the file containing +// pos. It ignores //line directives. +func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) { + file := pass.Fset.PositionFor(pos, false).Filename + m := pass.ResultOf[facts.Generated].(map[string]facts.Generator) + g, ok := m[file] + return g, ok +} + +// MayHaveSideEffects reports whether expr may have side effects. If +// the purity argument is nil, this function implements a purely +// syntactic check, meaning that any function call may have side +// effects, regardless of the called function's body. Otherwise, +// purity will be consulted to determine the purity of function calls. +func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity facts.PurityResult) bool { + switch expr := expr.(type) { + case *ast.BadExpr: + return true + case *ast.Ellipsis: + return MayHaveSideEffects(pass, expr.Elt, purity) + case *ast.FuncLit: + // the literal itself cannot have side ffects, only calling it + // might, which is handled by CallExpr. + return false + case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: + // types cannot have side effects + return false + case *ast.BasicLit: + return false + case *ast.BinaryExpr: + return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity) + case *ast.CallExpr: + if purity == nil { + return true + } + switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) { + case *types.Func: + if _, ok := purity[obj]; !ok { + return true + } + case *types.Builtin: + switch obj.Name() { + case "len", "cap": + default: + return true + } + default: + return true + } + for _, arg := range expr.Args { + if MayHaveSideEffects(pass, arg, purity) { + return true + } + } + return false + case *ast.CompositeLit: + if MayHaveSideEffects(pass, expr.Type, purity) { + return true + } + for _, elt := range expr.Elts { + if MayHaveSideEffects(pass, elt, purity) { + return true + } + } + return false + case *ast.Ident: + return false + case *ast.IndexExpr: + return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity) + case *ast.KeyValueExpr: + return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity) + case *ast.SelectorExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case *ast.SliceExpr: + return MayHaveSideEffects(pass, expr.X, purity) || + MayHaveSideEffects(pass, expr.Low, purity) || + MayHaveSideEffects(pass, expr.High, purity) || + MayHaveSideEffects(pass, expr.Max, purity) + case *ast.StarExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case *ast.TypeAssertExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case *ast.UnaryExpr: + if MayHaveSideEffects(pass, expr.X, purity) { + return true + } + return expr.Op == token.ARROW + case *ast.ParenExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case nil: + return false + default: + panic(fmt.Sprintf("internal error: unhandled type %T", expr)) + } +} + +func IsGoVersion(pass *analysis.Pass, minor int) bool { + version := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter).Get().(int) + return version >= minor +} + +func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) { + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn) +} diff --git a/vendor/honnef.co/go/tools/config/config.go b/vendor/honnef.co/go/tools/config/config.go index c22093a6d9..55115371b9 100644 --- a/vendor/honnef.co/go/tools/config/config.go +++ b/vendor/honnef.co/go/tools/config/config.go @@ -3,6 +3,8 @@ package config import ( "bytes" "fmt" + "go/ast" + "go/token" "os" "path/filepath" "reflect" @@ -12,38 +14,57 @@ import ( "golang.org/x/tools/go/analysis" ) +// Dir looks at a list of absolute file names, which should make up a +// single package, and returns the path of the directory that may +// contain a staticcheck.conf file. It returns the empty string if no +// such directory could be determined, for example because all files +// were located in Go's build cache. +func Dir(files []string) string { + if len(files) == 0 { + return "" + } + cache, err := os.UserCacheDir() + if err != nil { + cache = "" + } + var path string + for _, p := range files { + // FIXME(dh): using strings.HasPrefix isn't technically + // correct, but it should be good enough for now. + if cache != "" && strings.HasPrefix(p, cache) { + // File in the build cache of the standard Go build system + continue + } + path = p + break + } + + if path == "" { + // The package only consists of generated files. + return "" + } + + dir := filepath.Dir(path) + return dir +} + +func dirAST(files []*ast.File, fset *token.FileSet) string { + names := make([]string, len(files)) + for i, f := range files { + names[i] = fset.PositionFor(f.Pos(), true).Filename + } + return Dir(names) +} + var Analyzer = &analysis.Analyzer{ Name: "config", Doc: "loads configuration for the current package tree", Run: func(pass *analysis.Pass) (interface{}, error) { - if len(pass.Files) == 0 { + dir := dirAST(pass.Files, pass.Fset) + if dir == "" { cfg := DefaultConfig return &cfg, nil } - cache, err := os.UserCacheDir() - if err != nil { - cache = "" - } - var path string - for _, f := range pass.Files { - p := pass.Fset.PositionFor(f.Pos(), true).Filename - // FIXME(dh): using strings.HasPrefix isn't technically - // correct, but it should be good enough for now. - if cache != "" && strings.HasPrefix(p, cache) { - // File in the build cache of the standard Go build system - continue - } - path = p - break - } - - if path == "" { - // The package only consists of generated files. - cfg := DefaultConfig - return &cfg, nil - } - - dir := filepath.Dir(path) cfg, err := Load(dir) if err != nil { return nil, fmt.Errorf("error loading staticcheck.conf: %s", err) @@ -136,7 +157,7 @@ func (c Config) String() string { } var DefaultConfig = Config{ - Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016"}, + Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"}, Initialisms: []string{ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", @@ -144,20 +165,20 @@ var DefaultConfig = Config{ "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", - "XSS", "SIP", "RTP", + "XSS", "SIP", "RTP", "AMQP", "DB", "TS", }, DotImportWhitelist: []string{}, HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"}, } -const configName = "staticcheck.conf" +const ConfigName = "staticcheck.conf" func parseConfigs(dir string) ([]Config, error) { var out []Config // TODO(dh): consider stopping at the GOPATH/module boundary for dir != "" { - f, err := os.Open(filepath.Join(dir, configName)) + f, err := os.Open(filepath.Join(dir, ConfigName)) if os.IsNotExist(err) { ndir := filepath.Dir(dir) if ndir == dir { diff --git a/vendor/honnef.co/go/tools/deprecated/stdlib.go b/vendor/honnef.co/go/tools/deprecated/stdlib.go index 5d8ce186b1..cabb8500a2 100644 --- a/vendor/honnef.co/go/tools/deprecated/stdlib.go +++ b/vendor/honnef.co/go/tools/deprecated/stdlib.go @@ -6,7 +6,6 @@ type Deprecation struct { } var Stdlib = map[string]Deprecation{ - "image/jpeg.Reader": {4, 0}, // FIXME(dh): AllowBinary isn't being detected as deprecated // because the comment has a newline right after "Deprecated:" "go/build.AllowBinary": {7, 7}, @@ -73,40 +72,48 @@ var Stdlib = map[string]Deprecation{ // This function has no alternative, but also no purpose. "(*crypto/rc4.Cipher).Reset": {12, 0}, "(net/http/httptest.ResponseRecorder).HeaderMap": {11, 7}, + "image.ZP": {13, 0}, + "image.ZR": {13, 0}, + "(*debug/gosym.LineTable).LineToPC": {2, 2}, + "(*debug/gosym.LineTable).PCToLine": {2, 2}, + "crypto/tls.VersionSSL30": {13, 0}, + "(crypto/tls.Config).NameToCertificate": {14, 14}, + "(*crypto/tls.Config).BuildNameToCertificate": {14, 14}, + "image/jpeg.Reader": {4, 0}, // All of these have been deprecated in favour of external libraries - "syscall.AttachLsf": {7, 0}, - "syscall.DetachLsf": {7, 0}, - "syscall.LsfSocket": {7, 0}, - "syscall.SetLsfPromisc": {7, 0}, - "syscall.LsfJump": {7, 0}, - "syscall.LsfStmt": {7, 0}, - "syscall.BpfStmt": {7, 0}, - "syscall.BpfJump": {7, 0}, - "syscall.BpfBuflen": {7, 0}, - "syscall.SetBpfBuflen": {7, 0}, - "syscall.BpfDatalink": {7, 0}, - "syscall.SetBpfDatalink": {7, 0}, - "syscall.SetBpfPromisc": {7, 0}, - "syscall.FlushBpf": {7, 0}, - "syscall.BpfInterface": {7, 0}, - "syscall.SetBpfInterface": {7, 0}, - "syscall.BpfTimeout": {7, 0}, - "syscall.SetBpfTimeout": {7, 0}, - "syscall.BpfStats": {7, 0}, - "syscall.SetBpfImmediate": {7, 0}, - "syscall.SetBpf": {7, 0}, - "syscall.CheckBpfVersion": {7, 0}, - "syscall.BpfHeadercmpl": {7, 0}, - "syscall.SetBpfHeadercmpl": {7, 0}, - "syscall.RouteRIB": {8, 0}, - "syscall.RoutingMessage": {8, 0}, - "syscall.RouteMessage": {8, 0}, - "syscall.InterfaceMessage": {8, 0}, - "syscall.InterfaceAddrMessage": {8, 0}, - "syscall.ParseRoutingMessage": {8, 0}, - "syscall.ParseRoutingSockaddr": {8, 0}, - "InterfaceAnnounceMessage": {7, 0}, - "InterfaceMulticastAddrMessage": {7, 0}, - "syscall.FormatMessage": {5, 0}, + "syscall.AttachLsf": {7, 0}, + "syscall.DetachLsf": {7, 0}, + "syscall.LsfSocket": {7, 0}, + "syscall.SetLsfPromisc": {7, 0}, + "syscall.LsfJump": {7, 0}, + "syscall.LsfStmt": {7, 0}, + "syscall.BpfStmt": {7, 0}, + "syscall.BpfJump": {7, 0}, + "syscall.BpfBuflen": {7, 0}, + "syscall.SetBpfBuflen": {7, 0}, + "syscall.BpfDatalink": {7, 0}, + "syscall.SetBpfDatalink": {7, 0}, + "syscall.SetBpfPromisc": {7, 0}, + "syscall.FlushBpf": {7, 0}, + "syscall.BpfInterface": {7, 0}, + "syscall.SetBpfInterface": {7, 0}, + "syscall.BpfTimeout": {7, 0}, + "syscall.SetBpfTimeout": {7, 0}, + "syscall.BpfStats": {7, 0}, + "syscall.SetBpfImmediate": {7, 0}, + "syscall.SetBpf": {7, 0}, + "syscall.CheckBpfVersion": {7, 0}, + "syscall.BpfHeadercmpl": {7, 0}, + "syscall.SetBpfHeadercmpl": {7, 0}, + "syscall.RouteRIB": {8, 0}, + "syscall.RoutingMessage": {8, 0}, + "syscall.RouteMessage": {8, 0}, + "syscall.InterfaceMessage": {8, 0}, + "syscall.InterfaceAddrMessage": {8, 0}, + "syscall.ParseRoutingMessage": {8, 0}, + "syscall.ParseRoutingSockaddr": {8, 0}, + "syscall.InterfaceAnnounceMessage": {7, 0}, + "syscall.InterfaceMulticastAddrMessage": {7, 0}, + "syscall.FormatMessage": {5, 0}, } diff --git a/vendor/honnef.co/go/tools/edit/edit.go b/vendor/honnef.co/go/tools/edit/edit.go new file mode 100644 index 0000000000..f4cfba2347 --- /dev/null +++ b/vendor/honnef.co/go/tools/edit/edit.go @@ -0,0 +1,67 @@ +package edit + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/pattern" +) + +type Ranger interface { + Pos() token.Pos + End() token.Pos +} + +type Range [2]token.Pos + +func (r Range) Pos() token.Pos { return r[0] } +func (r Range) End() token.Pos { return r[1] } + +func ReplaceWithString(fset *token.FileSet, old Ranger, new string) analysis.TextEdit { + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: []byte(new), + } +} + +func ReplaceWithNode(fset *token.FileSet, old Ranger, new ast.Node) analysis.TextEdit { + buf := &bytes.Buffer{} + if err := format.Node(buf, fset, new); err != nil { + panic("internal error: " + err.Error()) + } + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: buf.Bytes(), + } +} + +func ReplaceWithPattern(pass *analysis.Pass, after pattern.Pattern, state pattern.State, node Ranger) analysis.TextEdit { + r := pattern.NodeToAST(after.Root, state) + buf := &bytes.Buffer{} + format.Node(buf, pass.Fset, r) + return analysis.TextEdit{ + Pos: node.Pos(), + End: node.End(), + NewText: buf.Bytes(), + } +} + +func Delete(old Ranger) analysis.TextEdit { + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: nil, + } +} + +func Fix(msg string, edits ...analysis.TextEdit) analysis.SuggestedFix { + return analysis.SuggestedFix{ + Message: msg, + TextEdits: edits, + } +} diff --git a/vendor/honnef.co/go/tools/facts/generated.go b/vendor/honnef.co/go/tools/facts/generated.go index 1ed9563a30..18cbb49bd9 100644 --- a/vendor/honnef.co/go/tools/facts/generated.go +++ b/vendor/honnef.co/go/tools/facts/generated.go @@ -19,6 +19,7 @@ const ( Goyacc Cgo Stringer + ProtocGenGo ) var ( @@ -51,10 +52,16 @@ func isGenerated(path string) (Generator, bool) { return Goyacc, true case "by cmd/cgo;": return Cgo, true + case "by protoc-gen-go.": + return ProtocGenGo, true } if strings.HasPrefix(text, `by "stringer `) { return Stringer, true } + if strings.HasPrefix(text, `by goyacc `) { + return Goyacc, true + } + return Unknown, true } if bytes.Equal(s, oldCgo) { diff --git a/vendor/honnef.co/go/tools/facts/purity.go b/vendor/honnef.co/go/tools/facts/purity.go index 861ca41104..099ee23e3b 100644 --- a/vendor/honnef.co/go/tools/facts/purity.go +++ b/vendor/honnef.co/go/tools/facts/purity.go @@ -1,14 +1,13 @@ package facts import ( - "go/token" "go/types" "reflect" "golang.org/x/tools/go/analysis" "honnef.co/go/tools/functions" - "honnef.co/go/tools/internal/passes/buildssa" - "honnef.co/go/tools/ssa" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/ir" ) type IsPure struct{} @@ -22,7 +21,7 @@ var Purity = &analysis.Analyzer{ Name: "fact_purity", Doc: "Mark pure functions", Run: purity, - Requires: []*analysis.Analyzer{buildssa.Analyzer}, + Requires: []*analysis.Analyzer{buildir.Analyzer}, FactTypes: []analysis.Fact{(*IsPure)(nil)}, ResultType: reflect.TypeOf(PurityResult{}), } @@ -56,65 +55,68 @@ var pureStdlib = map[string]struct{}{ } func purity(pass *analysis.Pass) (interface{}, error) { - seen := map[*ssa.Function]struct{}{} - ssapkg := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).Pkg - var check func(ssafn *ssa.Function) (ret bool) - check = func(ssafn *ssa.Function) (ret bool) { - if ssafn.Object() == nil { + seen := map[*ir.Function]struct{}{} + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + var check func(fn *ir.Function) (ret bool) + check = func(fn *ir.Function) (ret bool) { + if fn.Object() == nil { // TODO(dh): support closures return false } - if pass.ImportObjectFact(ssafn.Object(), new(IsPure)) { + if pass.ImportObjectFact(fn.Object(), new(IsPure)) { return true } - if ssafn.Pkg != ssapkg { + if fn.Pkg != irpkg { // Function is in another package but wasn't marked as // pure, ergo it isn't pure return false } // Break recursion - if _, ok := seen[ssafn]; ok { + if _, ok := seen[fn]; ok { return false } - seen[ssafn] = struct{}{} + seen[fn] = struct{}{} defer func() { if ret { - pass.ExportObjectFact(ssafn.Object(), &IsPure{}) + pass.ExportObjectFact(fn.Object(), &IsPure{}) } }() - if functions.IsStub(ssafn) { + if functions.IsStub(fn) { return false } - if _, ok := pureStdlib[ssafn.Object().(*types.Func).FullName()]; ok { + if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok { return true } - if ssafn.Signature.Results().Len() == 0 { + if fn.Signature.Results().Len() == 0 { // A function with no return values is empty or is doing some // work we cannot see (for example because of build tags); // don't consider it pure. return false } - for _, param := range ssafn.Params { + for _, param := range fn.Params { + // TODO(dh): this may not be strictly correct. pure code + // can, to an extent, operate on non-basic types. if _, ok := param.Type().Underlying().(*types.Basic); !ok { return false } } - if ssafn.Blocks == nil { + // Don't consider external functions pure. + if fn.Blocks == nil { return false } - checkCall := func(common *ssa.CallCommon) bool { + checkCall := func(common *ir.CallCommon) bool { if common.IsInvoke() { return false } - builtin, ok := common.Value.(*ssa.Builtin) + builtin, ok := common.Value.(*ir.Builtin) if !ok { - if common.StaticCallee() != ssafn { + if common.StaticCallee() != fn { if common.StaticCallee() == nil { return false } @@ -124,47 +126,47 @@ func purity(pass *analysis.Pass) (interface{}, error) { } } else { switch builtin.Name() { - case "len", "cap", "make", "new": + case "len", "cap": default: return false } } return true } - for _, b := range ssafn.Blocks { + for _, b := range fn.Blocks { for _, ins := range b.Instrs { switch ins := ins.(type) { - case *ssa.Call: + case *ir.Call: if !checkCall(ins.Common()) { return false } - case *ssa.Defer: + case *ir.Defer: if !checkCall(&ins.Call) { return false } - case *ssa.Select: + case *ir.Select: return false - case *ssa.Send: + case *ir.Send: return false - case *ssa.Go: + case *ir.Go: return false - case *ssa.Panic: + case *ir.Panic: return false - case *ssa.Store: + case *ir.Store: return false - case *ssa.FieldAddr: + case *ir.FieldAddr: + return false + case *ir.Alloc: + return false + case *ir.Load: return false - case *ssa.UnOp: - if ins.Op == token.MUL || ins.Op == token.AND { - return false - } } } } return true } - for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs { - check(ssafn) + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + check(fn) } out := PurityResult{} diff --git a/vendor/honnef.co/go/tools/functions/loops.go b/vendor/honnef.co/go/tools/functions/loops.go index 15877a2f96..a8af701008 100644 --- a/vendor/honnef.co/go/tools/functions/loops.go +++ b/vendor/honnef.co/go/tools/functions/loops.go @@ -1,10 +1,10 @@ package functions -import "honnef.co/go/tools/ssa" +import "honnef.co/go/tools/ir" -type Loop struct{ ssa.BlockSet } +type Loop struct{ *ir.BlockSet } -func FindLoops(fn *ssa.Function) []Loop { +func FindLoops(fn *ir.Function) []Loop { if fn.Blocks == nil { return nil } @@ -18,12 +18,12 @@ func FindLoops(fn *ssa.Function) []Loop { // n is a back-edge to h // h is the loop header if n == h { - set := Loop{} + set := Loop{ir.NewBlockSet(len(fn.Blocks))} set.Add(n) sets = append(sets, set) continue } - set := Loop{} + set := Loop{ir.NewBlockSet(len(fn.Blocks))} set.Add(h) set.Add(n) for _, b := range allPredsBut(n, h, nil) { @@ -35,7 +35,7 @@ func FindLoops(fn *ssa.Function) []Loop { return sets } -func allPredsBut(b, but *ssa.BasicBlock, list []*ssa.BasicBlock) []*ssa.BasicBlock { +func allPredsBut(b, but *ir.BasicBlock, list []*ir.BasicBlock) []*ir.BasicBlock { outer: for _, pred := range b.Preds { if pred == but { diff --git a/vendor/honnef.co/go/tools/functions/pure.go b/vendor/honnef.co/go/tools/functions/pure.go deleted file mode 100644 index 8bc5587713..0000000000 --- a/vendor/honnef.co/go/tools/functions/pure.go +++ /dev/null @@ -1,46 +0,0 @@ -package functions - -import ( - "honnef.co/go/tools/ssa" -) - -func filterDebug(instr []ssa.Instruction) []ssa.Instruction { - var out []ssa.Instruction - for _, ins := range instr { - if _, ok := ins.(*ssa.DebugRef); !ok { - out = append(out, ins) - } - } - return out -} - -// IsStub reports whether a function is a stub. A function is -// considered a stub if it has no instructions or exactly one -// instruction, which must be either returning only constant values or -// a panic. -func IsStub(fn *ssa.Function) bool { - if len(fn.Blocks) == 0 { - return true - } - if len(fn.Blocks) > 1 { - return false - } - instrs := filterDebug(fn.Blocks[0].Instrs) - if len(instrs) != 1 { - return false - } - - switch instrs[0].(type) { - case *ssa.Return: - // Since this is the only instruction, the return value must - // be a constant. We consider all constants as stubs, not just - // the zero value. This does not, unfortunately, cover zero - // initialised structs, as these cause additional - // instructions. - return true - case *ssa.Panic: - return true - default: - return false - } -} diff --git a/vendor/honnef.co/go/tools/functions/stub.go b/vendor/honnef.co/go/tools/functions/stub.go new file mode 100644 index 0000000000..4d5de10b85 --- /dev/null +++ b/vendor/honnef.co/go/tools/functions/stub.go @@ -0,0 +1,32 @@ +package functions + +import ( + "honnef.co/go/tools/ir" +) + +// IsStub reports whether a function is a stub. A function is +// considered a stub if it has no instructions or if all it does is +// return a constant value. +func IsStub(fn *ir.Function) bool { + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + switch instr.(type) { + case *ir.Const: + // const naturally has no side-effects + case *ir.Panic: + // panic is a stub if it only uses constants + case *ir.Return: + // return is a stub if it only uses constants + case *ir.DebugRef: + case *ir.Jump: + // if there are no disallowed instructions, then we're + // only jumping to the exit block (or possibly + // somewhere else that's stubby?) + default: + // all other instructions are assumed to do actual work + return false + } + } + } + return true +} diff --git a/vendor/honnef.co/go/tools/functions/terminates.go b/vendor/honnef.co/go/tools/functions/terminates.go index 3e9c3a23f3..c4984673f6 100644 --- a/vendor/honnef.co/go/tools/functions/terminates.go +++ b/vendor/honnef.co/go/tools/functions/terminates.go @@ -1,11 +1,15 @@ package functions -import "honnef.co/go/tools/ssa" +import ( + "go/types" + + "honnef.co/go/tools/ir" +) // Terminates reports whether fn is supposed to return, that is if it // has at least one theoretic path that returns from the function. // Explicit panics do not count as terminating. -func Terminates(fn *ssa.Function) bool { +func Terminates(fn *ir.Function) bool { if fn.Blocks == nil { // assuming that a function terminates is the conservative // choice @@ -13,11 +17,53 @@ func Terminates(fn *ssa.Function) bool { } for _, block := range fn.Blocks { - if len(block.Instrs) == 0 { - continue - } - if _, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return); ok { - return true + if _, ok := block.Control().(*ir.Return); ok { + if len(block.Preds) == 0 { + return true + } + for _, pred := range block.Preds { + switch ctrl := pred.Control().(type) { + case *ir.Panic: + // explicit panics do not count as terminating + case *ir.If: + // Check if we got here by receiving from a closed + // time.Tick channel – this cannot happen at + // runtime and thus doesn't constitute termination + iff := ctrl + if !ok { + return true + } + ex, ok := iff.Cond.(*ir.Extract) + if !ok { + return true + } + if ex.Index != 1 { + return true + } + recv, ok := ex.Tuple.(*ir.Recv) + if !ok { + return true + } + call, ok := recv.Chan.(*ir.Call) + if !ok { + return true + } + fn, ok := call.Common().Value.(*ir.Function) + if !ok { + return true + } + fn2, ok := fn.Object().(*types.Func) + if !ok { + return true + } + if fn2.FullName() != "time.Tick" { + return true + } + default: + // we've reached the exit block + return true + } + } } } return false diff --git a/vendor/honnef.co/go/tools/internal/cache/cache.go b/vendor/honnef.co/go/tools/internal/cache/cache.go index 2b33ca1067..6b41811cf2 100644 --- a/vendor/honnef.co/go/tools/internal/cache/cache.go +++ b/vendor/honnef.co/go/tools/internal/cache/cache.go @@ -177,7 +177,7 @@ func (c *Cache) get(id ActionID) (Entry, error) { i++ } tm, err := strconv.ParseInt(string(etime[i:]), 10, 64) - if err != nil || size < 0 { + if err != nil || tm < 0 { return missing() } @@ -265,7 +265,7 @@ func (c *Cache) Trim() { // We maintain in dir/trim.txt the time of the last completed cache trim. // If the cache has been trimmed recently enough, do nothing. // This is the common case. - data, _ := ioutil.ReadFile(filepath.Join(c.dir, "trim.txt")) + data, _ := renameio.ReadFile(filepath.Join(c.dir, "trim.txt")) t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64) if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval { return @@ -282,7 +282,7 @@ func (c *Cache) Trim() { // Ignore errors from here: if we don't write the complete timestamp, the // cache will appear older than it is, and we'll trim it again next time. - renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix()))) + renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), []byte(fmt.Sprintf("%d", now.Unix())), 0666) } // trimSubdir trims a single cache subdirectory. @@ -326,7 +326,8 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify // in verify mode we are double-checking that the cache entries // are entirely reproducible. As just noted, this may be unrealistic // in some cases but the check is also useful for shaking out real bugs. - entry := []byte(fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano())) + entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano()) + if verify && allowVerify { old, err := c.get(id) if err == nil && (old.OutputID != out || old.Size != size) { @@ -336,7 +337,28 @@ func (c *Cache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify } } file := c.fileName(id, "a") - if err := ioutil.WriteFile(file, entry, 0666); err != nil { + + // Copy file to cache directory. + mode := os.O_WRONLY | os.O_CREATE + f, err := os.OpenFile(file, mode, 0666) + if err != nil { + return err + } + _, err = f.WriteString(entry) + if err == nil { + // Truncate the file only *after* writing it. + // (This should be a no-op, but truncate just in case of previous corruption.) + // + // This differs from ioutil.WriteFile, which truncates to 0 *before* writing + // via os.O_TRUNC. Truncating only after writing ensures that a second write + // of the same content to the same file is idempotent, and does not — even + // temporarily! — undo the effect of the first write. + err = f.Truncate(int64(len(entry))) + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if err != nil { // TODO(bcmills): This Remove potentially races with another go command writing to file. // Can we eliminate it? os.Remove(file) diff --git a/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go b/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go new file mode 100644 index 0000000000..3946977024 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go @@ -0,0 +1,113 @@ +// Copyright 2018 The Go 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 buildir defines an Analyzer that constructs the IR +// of an error-free package and returns the set of all +// functions within it. It does not report any diagnostics itself but +// may be used as an input to other analyzers. +// +// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE. +package buildir + +import ( + "go/ast" + "go/types" + "reflect" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/ir" +) + +type willExit struct{} +type willUnwind struct{} + +func (*willExit) AFact() {} +func (*willUnwind) AFact() {} + +var Analyzer = &analysis.Analyzer{ + Name: "buildir", + Doc: "build IR for later passes", + Run: run, + ResultType: reflect.TypeOf(new(IR)), + FactTypes: []analysis.Fact{new(willExit), new(willUnwind)}, +} + +// IR provides intermediate representation for all the +// non-blank source functions in the current package. +type IR struct { + Pkg *ir.Package + SrcFuncs []*ir.Function +} + +func run(pass *analysis.Pass) (interface{}, error) { + // Plundered from ssautil.BuildPackage. + + // We must create a new Program for each Package because the + // analysis API provides no place to hang a Program shared by + // all Packages. Consequently, IR Packages and Functions do not + // have a canonical representation across an analysis session of + // multiple packages. This is unlikely to be a problem in + // practice because the analysis API essentially forces all + // packages to be analysed independently, so any given call to + // Analysis.Run on a package will see only IR objects belonging + // to a single Program. + + mode := ir.GlobalDebug + + prog := ir.NewProgram(pass.Fset, mode) + + // Create IR packages for all imports. + // Order is not significant. + created := make(map[*types.Package]bool) + var createAll func(pkgs []*types.Package) + createAll = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !created[p] { + created[p] = true + irpkg := prog.CreatePackage(p, nil, nil, true) + for _, fn := range irpkg.Functions { + if ast.IsExported(fn.Name()) { + var exit willExit + var unwind willUnwind + if pass.ImportObjectFact(fn.Object(), &exit) { + fn.WillExit = true + } + if pass.ImportObjectFact(fn.Object(), &unwind) { + fn.WillUnwind = true + } + } + } + createAll(p.Imports()) + } + } + } + createAll(pass.Pkg.Imports()) + + // Create and build the primary package. + irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false) + irpkg.Build() + + // Compute list of source functions, including literals, + // in source order. + var addAnons func(f *ir.Function) + funcs := make([]*ir.Function, len(irpkg.Functions)) + copy(funcs, irpkg.Functions) + addAnons = func(f *ir.Function) { + for _, anon := range f.AnonFuncs { + funcs = append(funcs, anon) + addAnons(anon) + } + } + for _, fn := range irpkg.Functions { + addAnons(fn) + if fn.WillExit { + pass.ExportObjectFact(fn.Object(), new(willExit)) + } + if fn.WillUnwind { + pass.ExportObjectFact(fn.Object(), new(willUnwind)) + } + } + + return &IR{Pkg: irpkg, SrcFuncs: funcs}, nil +} diff --git a/vendor/honnef.co/go/tools/internal/passes/buildssa/buildssa.go b/vendor/honnef.co/go/tools/internal/passes/buildssa/buildssa.go deleted file mode 100644 index fde918d121..0000000000 --- a/vendor/honnef.co/go/tools/internal/passes/buildssa/buildssa.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2018 The Go 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 buildssa defines an Analyzer that constructs the SSA -// representation of an error-free package and returns the set of all -// functions within it. It does not report any diagnostics itself but -// may be used as an input to other analyzers. -// -// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE. -package buildssa - -import ( - "go/ast" - "go/types" - "reflect" - - "golang.org/x/tools/go/analysis" - "honnef.co/go/tools/ssa" -) - -var Analyzer = &analysis.Analyzer{ - Name: "buildssa", - Doc: "build SSA-form IR for later passes", - Run: run, - ResultType: reflect.TypeOf(new(SSA)), -} - -// SSA provides SSA-form intermediate representation for all the -// non-blank source functions in the current package. -type SSA struct { - Pkg *ssa.Package - SrcFuncs []*ssa.Function -} - -func run(pass *analysis.Pass) (interface{}, error) { - // Plundered from ssautil.BuildPackage. - - // We must create a new Program for each Package because the - // analysis API provides no place to hang a Program shared by - // all Packages. Consequently, SSA Packages and Functions do not - // have a canonical representation across an analysis session of - // multiple packages. This is unlikely to be a problem in - // practice because the analysis API essentially forces all - // packages to be analysed independently, so any given call to - // Analysis.Run on a package will see only SSA objects belonging - // to a single Program. - - mode := ssa.GlobalDebug - - prog := ssa.NewProgram(pass.Fset, mode) - - // Create SSA packages for all imports. - // Order is not significant. - created := make(map[*types.Package]bool) - var createAll func(pkgs []*types.Package) - createAll = func(pkgs []*types.Package) { - for _, p := range pkgs { - if !created[p] { - created[p] = true - prog.CreatePackage(p, nil, nil, true) - createAll(p.Imports()) - } - } - } - createAll(pass.Pkg.Imports()) - - // Create and build the primary package. - ssapkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false) - ssapkg.Build() - - // Compute list of source functions, including literals, - // in source order. - var funcs []*ssa.Function - var addAnons func(f *ssa.Function) - addAnons = func(f *ssa.Function) { - funcs = append(funcs, f) - for _, anon := range f.AnonFuncs { - addAnons(anon) - } - } - addAnons(ssapkg.Members["init"].(*ssa.Function)) - for _, f := range pass.Files { - for _, decl := range f.Decls { - if fdecl, ok := decl.(*ast.FuncDecl); ok { - - // SSA will not build a Function - // for a FuncDecl named blank. - // That's arguably too strict but - // relaxing it would break uniqueness of - // names of package members. - if fdecl.Name.Name == "_" { - continue - } - - // (init functions have distinct Func - // objects named "init" and distinct - // ssa.Functions named "init#1", ...) - - fn := pass.TypesInfo.Defs[fdecl.Name].(*types.Func) - if fn == nil { - panic(fn) - } - - f := ssapkg.Prog.FuncValue(fn) - if f == nil { - panic(fn) - } - - addAnons(f) - } - } - } - - return &SSA{Pkg: ssapkg, SrcFuncs: funcs}, nil -} diff --git a/vendor/honnef.co/go/tools/internal/renameio/renameio.go b/vendor/honnef.co/go/tools/internal/renameio/renameio.go index 3f3f1708fa..a279d1a1eb 100644 --- a/vendor/honnef.co/go/tools/internal/renameio/renameio.go +++ b/vendor/honnef.co/go/tools/internal/renameio/renameio.go @@ -8,15 +8,15 @@ package renameio import ( "bytes" "io" - "io/ioutil" + "math/rand" "os" "path/filepath" - "runtime" - "strings" - "time" + "strconv" + + "honnef.co/go/tools/internal/robustio" ) -const patternSuffix = "*.tmp" +const patternSuffix = ".tmp" // Pattern returns a glob pattern that matches the unrenamed temporary files // created when writing to filename. @@ -29,14 +29,14 @@ func Pattern(filename string) string { // final name. // // That ensures that the final location, if it exists, is always a complete file. -func WriteFile(filename string, data []byte) (err error) { - return WriteToFile(filename, bytes.NewReader(data)) +func WriteFile(filename string, data []byte, perm os.FileMode) (err error) { + return WriteToFile(filename, bytes.NewReader(data), perm) } // WriteToFile is a variant of WriteFile that accepts the data as an io.Reader // instead of a slice. -func WriteToFile(filename string, data io.Reader) (err error) { - f, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename)+patternSuffix) +func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) { + f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm) if err != nil { return err } @@ -63,21 +63,31 @@ func WriteToFile(filename string, data io.Reader) (err error) { return err } - var start time.Time - for { - err := os.Rename(f.Name(), filename) - if err == nil || runtime.GOOS != "windows" || !strings.HasSuffix(err.Error(), "Access is denied.") { - return err - } + return robustio.Rename(f.Name(), filename) +} - // Windows seems to occasionally trigger spurious "Access is denied" errors - // here (see golang.org/issue/31247). We're not sure why. It's probably - // worth a little extra latency to avoid propagating the spurious errors. - if start.IsZero() { - start = time.Now() - } else if time.Since(start) >= 500*time.Millisecond { - return err +// tempFile creates a new temporary file with given permission bits. +func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) { + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix) + f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm) + if os.IsExist(err) { + continue } - time.Sleep(5 * time.Millisecond) + break } + return +} + +// ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that +// may occur if the file is concurrently replaced. +// +// Errors are classified heuristically and retries are bounded, so even this +// function may occasionally return a spurious error on Windows. +// If so, the error will likely wrap one of: +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION +func ReadFile(filename string) ([]byte, error) { + return robustio.ReadFile(filename) } diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio.go b/vendor/honnef.co/go/tools/internal/robustio/robustio.go new file mode 100644 index 0000000000..76e47ad1ff --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio.go @@ -0,0 +1,53 @@ +// Copyright 2019 The Go 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 robustio wraps I/O functions that are prone to failure on Windows, +// transparently retrying errors up to an arbitrary timeout. +// +// Errors are classified heuristically and retries are bounded, so the functions +// in this package do not completely eliminate spurious errors. However, they do +// significantly reduce the rate of failure in practice. +// +// If so, the error will likely wrap one of: +// The functions in this package do not completely eliminate spurious errors, +// but substantially reduce their rate of occurrence in practice. +package robustio + +// Rename is like os.Rename, but on Windows retries errors that may occur if the +// file is concurrently read or overwritten. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func Rename(oldpath, newpath string) error { + return rename(oldpath, newpath) +} + +// ReadFile is like ioutil.ReadFile, but on Windows retries errors that may +// occur if the file is concurrently replaced. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func ReadFile(filename string) ([]byte, error) { + return readFile(filename) +} + +// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur +// if an executable file in the directory has recently been executed. +// +// (See golang.org/issue/19491.) +func RemoveAll(path string) error { + return removeAll(path) +} + +// IsEphemeralError reports whether err is one of the errors that the functions +// in this package attempt to mitigate. +// +// Errors considered ephemeral include: +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION +// +// This set may be expanded in the future; programs must not rely on the +// non-ephemerality of any given error. +func IsEphemeralError(err error) bool { + return isEphemeralError(err) +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go new file mode 100644 index 0000000000..1ac0d10d7f --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go @@ -0,0 +1,29 @@ +// Copyright 2019 The Go 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 robustio + +import ( + "os" + "syscall" +) + +const errFileNotFound = syscall.ENOENT + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + switch werr := err.(type) { + case *os.PathError: + err = werr.Err + case *os.LinkError: + err = werr.Err + case *os.SyscallError: + err = werr.Err + + } + if errno, ok := err.(syscall.Errno); ok { + return errno == errFileNotFound + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go new file mode 100644 index 0000000000..e0bf5b9b3b --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go @@ -0,0 +1,93 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows darwin + +package robustio + +import ( + "io/ioutil" + "math/rand" + "os" + "syscall" + "time" +) + +const arbitraryTimeout = 500 * time.Millisecond + +const ERROR_SHARING_VIOLATION = 32 + +// retry retries ephemeral errors from f up to an arbitrary timeout +// to work around filesystem flakiness on Windows and Darwin. +func retry(f func() (err error, mayRetry bool)) error { + var ( + bestErr error + lowestErrno syscall.Errno + start time.Time + nextSleep time.Duration = 1 * time.Millisecond + ) + for { + err, mayRetry := f() + if err == nil || !mayRetry { + return err + } + + if errno, ok := err.(syscall.Errno); ok && (lowestErrno == 0 || errno < lowestErrno) { + bestErr = err + lowestErrno = errno + } else if bestErr == nil { + bestErr = err + } + + if start.IsZero() { + start = time.Now() + } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { + break + } + time.Sleep(nextSleep) + nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) + } + + return bestErr +} + +// rename is like os.Rename, but retries ephemeral errors. +// +// On windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with +// MOVEFILE_REPLACE_EXISTING. +// +// Windows also provides a different system call, ReplaceFile, +// that provides similar semantics, but perhaps preserves more metadata. (The +// documentation on the differences between the two is very sparse.) +// +// Empirical error rates with MoveFileEx are lower under modest concurrency, so +// for now we're sticking with what the os package already provides. +func rename(oldpath, newpath string) (err error) { + return retry(func() (err error, mayRetry bool) { + err = os.Rename(oldpath, newpath) + return err, isEphemeralError(err) + }) +} + +// readFile is like ioutil.ReadFile, but retries ephemeral errors. +func readFile(filename string) ([]byte, error) { + var b []byte + err := retry(func() (err error, mayRetry bool) { + b, err = ioutil.ReadFile(filename) + + // Unlike in rename, we do not retry errFileNotFound here: it can occur + // as a spurious error, but the file may also genuinely not exist, so the + // increase in robustness is probably not worth the extra latency. + + return err, isEphemeralError(err) && err != errFileNotFound + }) + return b, err +} + +func removeAll(path string) error { + return retry(func() (err error, mayRetry bool) { + err = os.RemoveAll(path) + return err, isEphemeralError(err) + }) +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_other.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_other.go new file mode 100644 index 0000000000..a2428856f2 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_other.go @@ -0,0 +1,28 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//+build !windows,!darwin + +package robustio + +import ( + "io/ioutil" + "os" +) + +func rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} + +func readFile(filename string) ([]byte, error) { + return ioutil.ReadFile(filename) +} + +func removeAll(path string) error { + return os.RemoveAll(path) +} + +func isEphemeralError(err error) bool { + return false +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go new file mode 100644 index 0000000000..a35237d44a --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go @@ -0,0 +1,33 @@ +// Copyright 2019 The Go 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 robustio + +import ( + "os" + "syscall" +) + +const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + switch werr := err.(type) { + case *os.PathError: + err = werr.Err + case *os.LinkError: + err = werr.Err + case *os.SyscallError: + err = werr.Err + } + if errno, ok := err.(syscall.Errno); ok { + switch errno { + case syscall.ERROR_ACCESS_DENIED, + syscall.ERROR_FILE_NOT_FOUND, + ERROR_SHARING_VIOLATION: + return true + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go b/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go index affee66072..e9abf0d893 100644 --- a/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go +++ b/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go @@ -5,23 +5,24 @@ import ( "go/types" "golang.org/x/tools/go/analysis" - "honnef.co/go/tools/internal/passes/buildssa" + "honnef.co/go/tools/code" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/ir" . "honnef.co/go/tools/lint/lintdsl" - "honnef.co/go/tools/ssa" ) func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) { - for _, ssafn := range pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs { - fn := func(node ast.Node) bool { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + cb := func(node ast.Node) bool { rng, ok := node.(*ast.RangeStmt) - if !ok || !IsBlank(rng.Key) { + if !ok || !code.IsBlank(rng.Key) { return true } - v, _ := ssafn.ValueForExpr(rng.X) + v, _ := fn.ValueForExpr(rng.X) // Check that we're converting from string to []rune - val, _ := v.(*ssa.Convert) + val, _ := v.(*ir.Convert) if val == nil { return true } @@ -47,13 +48,13 @@ func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) { // Expect two refs: one for obtaining the length of the slice, // one for accessing the elements - if len(FilterDebug(*refs)) != 2 { + if len(code.FilterDebug(*refs)) != 2 { // TODO(dh): right now, we check that only one place // refers to our slice. This will miss cases such as // ranging over the slice twice. Ideally, we'd ensure that // the slice is only used for ranging over (without // accessing the key), but that is harder to do because in - // SSA form, ranging over a slice looks like an ordinary + // IR form, ranging over a slice looks like an ordinary // loop with index increments and slice accesses. We'd // have to look at the associated AST node to check that // it's a range statement. @@ -64,7 +65,7 @@ func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) { return true } - Inspect(ssafn.Syntax(), fn) + Inspect(fn.Source(), cb) } return nil, nil } diff --git a/vendor/honnef.co/go/tools/ssa/LICENSE b/vendor/honnef.co/go/tools/ir/LICENSE similarity index 100% rename from vendor/honnef.co/go/tools/ssa/LICENSE rename to vendor/honnef.co/go/tools/ir/LICENSE diff --git a/vendor/honnef.co/go/tools/ssa/blockopt.go b/vendor/honnef.co/go/tools/ir/blockopt.go similarity index 83% rename from vendor/honnef.co/go/tools/ssa/blockopt.go rename to vendor/honnef.co/go/tools/ir/blockopt.go index 22c9a4c0d4..d7a0e35676 100644 --- a/vendor/honnef.co/go/tools/ssa/blockopt.go +++ b/vendor/honnef.co/go/tools/ir/blockopt.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // Simple block optimizations to simplify the control flow graph. @@ -21,35 +21,34 @@ const debugBlockOpt = false // markReachable sets Index=-1 for all blocks reachable from b. func markReachable(b *BasicBlock) { - b.Index = -1 + b.gaps = -1 for _, succ := range b.Succs { - if succ.Index == 0 { + if succ.gaps == 0 { markReachable(succ) } } } -func DeleteUnreachableBlocks(f *Function) { - deleteUnreachableBlocks(f) -} - // deleteUnreachableBlocks marks all reachable blocks of f and // eliminates (nils) all others, including possibly cyclic subgraphs. // func deleteUnreachableBlocks(f *Function) { const white, black = 0, -1 - // We borrow b.Index temporarily as the mark bit. + // We borrow b.gaps temporarily as the mark bit. for _, b := range f.Blocks { - b.Index = white + b.gaps = white } markReachable(f.Blocks[0]) - if f.Recover != nil { - markReachable(f.Recover) - } + // In SSI form, we need the exit to be reachable for correct + // post-dominance information. In original form, however, we + // cannot unconditionally mark it reachable because we won't + // be adding fake edges, and this breaks the calculation of + // dominance information. + markReachable(f.Exit) for i, b := range f.Blocks { - if b.Index == white { + if b.gaps == white { for _, c := range b.Succs { - if c.Index == black { + if c.gaps == black { c.removePred(b) // delete white->black edge } } @@ -73,6 +72,13 @@ func jumpThreading(f *Function, b *BasicBlock) bool { if b.Instrs == nil { return false } + for _, pred := range b.Preds { + switch pred.Control().(type) { + case *ConstantSwitch: + // don't optimize away the head blocks of switch statements + return false + } + } if _, ok := b.Instrs[0].(*Jump); !ok { return false // not just a jump } @@ -117,10 +123,17 @@ func fuseBlocks(f *Function, a *BasicBlock) bool { if len(a.Succs) != 1 { return false } + if a.Succs[0] == f.Exit { + return false + } b := a.Succs[0] if len(b.Preds) != 1 { return false } + if _, ok := a.Instrs[len(a.Instrs)-1].(*Panic); ok { + // panics aren't simple jumps, they have side effects. + return false + } // Degenerate &&/|| ops may result in a straight-line CFG // containing φ-nodes. (Ideally we'd replace such them with @@ -151,15 +164,16 @@ func fuseBlocks(f *Function, a *BasicBlock) bool { return true } -func OptimizeBlocks(f *Function) { - optimizeBlocks(f) -} - // optimizeBlocks() performs some simple block optimizations on a // completed function: dead block elimination, block fusion, jump // threading. // func optimizeBlocks(f *Function) { + if debugBlockOpt { + f.WriteTo(os.Stderr) + mustSanityCheck(f, nil) + } + deleteUnreachableBlocks(f) // Loop until no further progress. diff --git a/vendor/honnef.co/go/tools/ssa/builder.go b/vendor/honnef.co/go/tools/ir/builder.go similarity index 71% rename from vendor/honnef.co/go/tools/ssa/builder.go rename to vendor/honnef.co/go/tools/ir/builder.go index 317ac06116..fdf4cb1a91 100644 --- a/vendor/honnef.co/go/tools/ssa/builder.go +++ b/vendor/honnef.co/go/tools/ir/builder.go @@ -2,27 +2,22 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir -// This file implements the BUILD phase of SSA construction. +// This file implements the BUILD phase of IR construction. // -// SSA construction has two phases, CREATE and BUILD. In the CREATE phase +// IR construction has two phases, CREATE and BUILD. In the CREATE phase // (create.go), all packages are constructed and type-checked and // definitions of all package members are created, method-sets are // computed, and wrapper methods are synthesized. -// ssa.Packages are created in arbitrary order. +// ir.Packages are created in arbitrary order. // // In the BUILD phase (builder.go), the builder traverses the AST of -// each Go source function and generates SSA instructions for the +// each Go source function and generates IR instructions for the // function body. Initializer expressions for package-level variables // are emitted to the package's init() function in the order specified // by go/types.Info.InitOrder, then code for each function in the // package is generated in lexical order. -// The BUILD phases for distinct packages are independent and are -// executed in parallel. -// -// TODO(adonovan): indeed, building functions is now embarrassingly parallel. -// Audit for concurrency then benchmark using more goroutines. // // The builder's and Program's indices (maps) are populated and // mutated during the CREATE phase, but during the BUILD phase they @@ -36,7 +31,6 @@ import ( "go/token" "go/types" "os" - "sync" ) type opaqueType struct { @@ -59,27 +53,25 @@ var ( tUntypedNil = types.Typ[types.UntypedNil] tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators tEface = types.NewInterfaceType(nil, nil).Complete() - - // SSA Value constants. - vZero = intConst(0) - vOne = intConst(1) - vTrue = NewConst(constant.MakeBool(true), tBool) ) // builder holds state associated with the package currently being built. -// Its methods contain all the logic for AST-to-SSA conversion. -type builder struct{} +// Its methods contain all the logic for AST-to-IR conversion. +type builder struct { + printFunc string + + blocksets [5]BlockSet +} // cond emits to fn code to evaluate boolean condition e and jump // to t or f depending on its value, performing various simplifications. // // Postcondition: fn.currentBlock is nil. // -func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) { +func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) *If { switch e := e.(type) { case *ast.ParenExpr: - b.cond(fn, e.X, t, f) - return + return b.cond(fn, e.X, t, f) case *ast.BinaryExpr: switch e.Op { @@ -87,21 +79,18 @@ func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) { ltrue := fn.newBasicBlock("cond.true") b.cond(fn, e.X, ltrue, f) fn.currentBlock = ltrue - b.cond(fn, e.Y, t, f) - return + return b.cond(fn, e.Y, t, f) case token.LOR: lfalse := fn.newBasicBlock("cond.false") b.cond(fn, e.X, t, lfalse) fn.currentBlock = lfalse - b.cond(fn, e.Y, t, f) - return + return b.cond(fn, e.Y, t, f) } case *ast.UnaryExpr: if e.Op == token.NOT { - b.cond(fn, e.X, f, t) - return + return b.cond(fn, e.X, f, t) } } @@ -111,7 +100,7 @@ func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) { // The value of a constant condition may be platform-specific, // and may cause blocks that are reachable in some configuration // to be hidden from subsequent analyses such as bug-finding tools. - emitIf(fn, b.expr(fn, e), t, f) + return emitIf(fn, b.expr(fn, e), t, f, e) } // logicalBinop emits code to fn to evaluate e, a &&- or @@ -131,11 +120,11 @@ func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value { switch e.Op { case token.LAND: b.cond(fn, e.X, rhs, done) - short = NewConst(constant.MakeBool(false), t) + short = emitConst(fn, NewConst(constant.MakeBool(false), t)) case token.LOR: b.cond(fn, e.X, done, rhs) - short = NewConst(constant.MakeBool(true), t) + short = emitConst(fn, NewConst(constant.MakeBool(true), t)) } // Is rhs unreachable? @@ -161,23 +150,21 @@ func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value { // The edge from e.Y to done carries the value of e.Y. fn.currentBlock = rhs edges = append(edges, b.expr(fn, e.Y)) - emitJump(fn, done) + emitJump(fn, done, e) fn.currentBlock = done - phi := &Phi{Edges: edges, Comment: e.Op.String()} - phi.pos = e.OpPos + phi := &Phi{Edges: edges} phi.typ = t - return done.emit(phi) + return done.emit(phi, e) } -// exprN lowers a multi-result expression e to SSA form, emitting code +// exprN lowers a multi-result expression e to IR form, emitting code // to fn and returning a single Value whose type is a *types.Tuple. // The caller must access the components via Extract. // // Multi-result expressions include CallExprs in a multi-value // assignment or return statement, and "value,ok" uses of -// TypeAssertExpr, IndexExpr (when X is a map), and UnaryExpr (when Op -// is token.ARROW). +// TypeAssertExpr, IndexExpr (when X is a map), and Recv. // func (b *builder) exprN(fn *Function, e ast.Expr) Value { typ := fn.Pkg.typeOf(e).(*types.Tuple) @@ -192,36 +179,28 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value { var c Call b.setCall(fn, e, &c.Call) c.typ = typ - return fn.emit(&c) + return fn.emit(&c, e) case *ast.IndexExpr: mapt := fn.Pkg.typeOf(e.X).Underlying().(*types.Map) - lookup := &Lookup{ + lookup := &MapLookup{ X: b.expr(fn, e.X), - Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key()), + Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e), CommaOk: true, } lookup.setType(typ) - lookup.setPos(e.Lbrack) - return fn.emit(lookup) + return fn.emit(lookup, e) case *ast.TypeAssertExpr: - return emitTypeTest(fn, b.expr(fn, e.X), typ.At(0).Type(), e.Lparen) + return emitTypeTest(fn, b.expr(fn, e.X), typ.At(0).Type(), e) case *ast.UnaryExpr: // must be receive <- - unop := &UnOp{ - Op: token.ARROW, - X: b.expr(fn, e.X), - CommaOk: true, - } - unop.setType(typ) - unop.setPos(e.OpPos) - return fn.emit(unop) + return emitRecv(fn, b.expr(fn, e.X), true, typ, e) } panic(fmt.Sprintf("exprN(%T) in %s", e, fn)) } -// builtin emits to fn SSA instructions to implement a call to the +// builtin emits to fn IR instructions to implement a call to the // built-in function obj with the specified arguments // and return type. It returns the value defined by the result. // @@ -229,7 +208,7 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value { // the caller should treat this like an ordinary library function // call. // -func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, pos token.Pos) Value { +func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, source ast.Node) Value { switch obj.Name() { case "make": switch typ.Underlying().(type) { @@ -243,23 +222,20 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ // treat make([]T, n, m) as new([m]T)[:n] cap := m.Int64() at := types.NewArray(typ.Underlying().(*types.Slice).Elem(), cap) - alloc := emitNew(fn, at, pos) - alloc.Comment = "makeslice" + alloc := emitNew(fn, at, source) v := &Slice{ X: alloc, High: n, } - v.setPos(pos) v.setType(typ) - return fn.emit(v) + return fn.emit(v, source) } v := &MakeSlice{ Len: n, Cap: m, } - v.setPos(pos) v.setType(typ) - return fn.emit(v) + return fn.emit(v, source) case *types.Map: var res Value @@ -267,24 +243,21 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ res = b.expr(fn, args[1]) } v := &MakeMap{Reserve: res} - v.setPos(pos) v.setType(typ) - return fn.emit(v) + return fn.emit(v, source) case *types.Chan: - var sz Value = vZero + var sz Value = emitConst(fn, intConst(0)) if len(args) == 2 { sz = b.expr(fn, args[1]) } v := &MakeChan{Size: sz} - v.setPos(pos) v.setType(typ) - return fn.emit(v) + return fn.emit(v, source) } case "new": - alloc := emitNew(fn, deref(typ), pos) - alloc.Comment = "new" + alloc := emitNew(fn, deref(typ), source) return alloc case "len", "cap": @@ -296,22 +269,22 @@ func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ t := deref(fn.Pkg.typeOf(args[0])).Underlying() if at, ok := t.(*types.Array); ok { b.expr(fn, args[0]) // for effects only - return intConst(at.Len()) + return emitConst(fn, intConst(at.Len())) } // Otherwise treat as normal. case "panic": fn.emit(&Panic{ - X: emitConv(fn, b.expr(fn, args[0]), tEface), - pos: pos, - }) + X: emitConv(fn, b.expr(fn, args[0]), tEface, source), + }, source) + addEdge(fn.currentBlock, fn.Exit) fn.currentBlock = fn.newBasicBlock("unreachable") - return vTrue // any non-nil Value will do + return emitConst(fn, NewConst(constant.MakeBool(true), tBool)) // any non-nil Value will do } return nil // treat all others as a regular function call } -// addr lowers a single-result addressable expression e to SSA form, +// addr lowers a single-result addressable expression e to IR form, // emitting code to fn and returning the location (an lvalue) defined // by the expression. // @@ -345,21 +318,20 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { if v == nil { v = fn.lookup(obj, escaping) } - return &address{addr: v, pos: e.Pos(), expr: e} + return &address{addr: v, expr: e} case *ast.CompositeLit: t := deref(fn.Pkg.typeOf(e)) var v *Alloc if escaping { - v = emitNew(fn, t, e.Lbrace) + v = emitNew(fn, t, e) } else { - v = fn.addLocal(t, e.Lbrace) + v = fn.addLocal(t, e) } - v.Comment = "complit" var sb storebuf b.compLit(fn, v, e, true, &sb) sb.emit(fn) - return &address{addr: v, pos: e.Lbrace, expr: e} + return &address{addr: v, expr: e} case *ast.ParenExpr: return b.addr(fn, e.X, escaping) @@ -374,11 +346,10 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { panic(sel) } wantAddr := true - v := b.receiver(fn, e.X, wantAddr, escaping, sel) + v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) last := len(sel.Index()) - 1 return &address{ addr: emitFieldSelection(fn, v, sel.Index()[last], true, e.Sel), - pos: e.Sel.Pos(), expr: e.Sel, } @@ -397,43 +368,42 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { et = types.NewPointer(t.Elem()) case *types.Map: return &element{ - m: b.expr(fn, e.X), - k: emitConv(fn, b.expr(fn, e.Index), t.Key()), - t: t.Elem(), - pos: e.Lbrack, + m: b.expr(fn, e.X), + k: emitConv(fn, b.expr(fn, e.Index), t.Key(), e.Index), + t: t.Elem(), } default: panic("unexpected container type in IndexExpr: " + t.String()) } v := &IndexAddr{ X: x, - Index: emitConv(fn, b.expr(fn, e.Index), tInt), + Index: emitConv(fn, b.expr(fn, e.Index), tInt, e.Index), } - v.setPos(e.Lbrack) v.setType(et) - return &address{addr: fn.emit(v), pos: e.Lbrack, expr: e} + return &address{addr: fn.emit(v, e), expr: e} case *ast.StarExpr: - return &address{addr: b.expr(fn, e.X), pos: e.Star, expr: e} + return &address{addr: b.expr(fn, e.X), expr: e} } panic(fmt.Sprintf("unexpected address expression: %T", e)) } type store struct { - lhs lvalue - rhs Value + lhs lvalue + rhs Value + source ast.Node } type storebuf struct{ stores []store } -func (sb *storebuf) store(lhs lvalue, rhs Value) { - sb.stores = append(sb.stores, store{lhs, rhs}) +func (sb *storebuf) store(lhs lvalue, rhs Value, source ast.Node) { + sb.stores = append(sb.stores, store{lhs, rhs, source}) } func (sb *storebuf) emit(fn *Function) { for _, s := range sb.stores { - s.lhs.store(fn, s.rhs) + s.lhs.store(fn, s.rhs, s.source) } } @@ -451,7 +421,7 @@ func (sb *storebuf) emit(fn *Function) { // in-place update of existing variables when the RHS is a composite // literal that may reference parts of the LHS. // -func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf) { +func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf, source ast.Node) { // Can we initialize it in place? if e, ok := unparen(e).(*ast.CompositeLit); ok { // A CompositeLit never evaluates to a pointer, @@ -462,9 +432,9 @@ func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb * ptr := b.addr(fn, e, true).address(fn) // copy address if sb != nil { - sb.store(loc, ptr) + sb.store(loc, ptr, source) } else { - loc.store(fn, ptr) + loc.store(fn, ptr, source) } return } @@ -501,13 +471,13 @@ func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb * // simple case: just copy rhs := b.expr(fn, e) if sb != nil { - sb.store(loc, rhs) + sb.store(loc, rhs, source) } else { - loc.store(fn, rhs) + loc.store(fn, rhs, source) } } -// expr lowers a single-result expression e to SSA form, emitting code +// expr lowers a single-result expression e to IR form, emitting code // to fn and returning the Value defined by the expression. // func (b *builder) expr(fn *Function, e ast.Expr) Value { @@ -517,7 +487,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value { // Is expression a constant? if tv.Value != nil { - return NewConst(tv.Value, tv.Type) + return emitConst(fn, NewConst(tv.Value, tv.Type)) } var v Value @@ -525,7 +495,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value { // Prefer pointer arithmetic ({Index,Field}Addr) followed // by Load over subelement extraction (e.g. Index, Field), // to avoid large copies. - v = b.addr(fn, e, false).load(fn) + v = b.addr(fn, e, false).load(fn, e) } else { v = b.expr0(fn, e, tv) } @@ -542,15 +512,16 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case *ast.FuncLit: fn2 := &Function{ - name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)), - Signature: fn.Pkg.typeOf(e.Type).Underlying().(*types.Signature), - pos: e.Type.Func, - parent: fn, - Pkg: fn.Pkg, - Prog: fn.Prog, - syntax: e, + name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)), + Signature: fn.Pkg.typeOf(e.Type).Underlying().(*types.Signature), + parent: fn, + Pkg: fn.Pkg, + Prog: fn.Prog, + functionBody: new(functionBody), } + fn2.source = e fn.AnonFuncs = append(fn.AnonFuncs, fn2) + fn2.initHTML(b.printFunc) b.buildFunction(fn2) if fn2.FreeVars == nil { return fn2 @@ -561,32 +532,22 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { v.Bindings = append(v.Bindings, fv.outer) fv.outer = nil } - return fn.emit(v) + return fn.emit(v, e) case *ast.TypeAssertExpr: // single-result form only - return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e.Lparen) + return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e) case *ast.CallExpr: if fn.Pkg.info.Types[e.Fun].IsType() { // Explicit type conversion, e.g. string(x) or big.Int(x) x := b.expr(fn, e.Args[0]) - y := emitConv(fn, x, tv.Type) - if y != x { - switch y := y.(type) { - case *Convert: - y.pos = e.Lparen - case *ChangeType: - y.pos = e.Lparen - case *MakeInterface: - y.pos = e.Lparen - } - } + y := emitConv(fn, x, tv.Type, e) return y } // Call to "intrinsic" built-ins, e.g. new, make, panic. if id, ok := unparen(e.Fun).(*ast.Ident); ok { if obj, ok := fn.Pkg.info.Uses[id].(*types.Builtin); ok { - if v := b.builtin(fn, obj, e.Args, tv.Type, e.Lparen); v != nil { + if v := b.builtin(fn, obj, e.Args, tv.Type, e); v != nil { return v } } @@ -595,7 +556,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { var v Call b.setCall(fn, e, &v.Call) v.setType(tv.Type) - return fn.emit(&v) + return fn.emit(&v, e) case *ast.UnaryExpr: switch e.Op { @@ -606,19 +567,20 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { // For simplicity, we'll just (suboptimally) rely // on the side effects of a load. // TODO(adonovan): emit dedicated nilcheck. - addr.load(fn) + addr.load(fn, e) } return addr.address(fn) case token.ADD: return b.expr(fn, e.X) - case token.NOT, token.ARROW, token.SUB, token.XOR: // ! <- - ^ + case token.NOT, token.SUB, token.XOR: // ! <- - ^ v := &UnOp{ Op: e.Op, X: b.expr(fn, e.X), } - v.setPos(e.OpPos) v.setType(tv.Type) - return fn.emit(v) + return fn.emit(v, e) + case token.ARROW: + return emitRecv(fn, b.expr(fn, e.X), false, tv.Type, e) default: panic(e.Op) } @@ -630,12 +592,12 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case token.SHL, token.SHR: fallthrough case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: - return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), tv.Type, e.OpPos) + return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), tv.Type, e) case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ: - cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e.OpPos) + cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e) // The type of x==y may be UntypedBool. - return emitConv(fn, cmp, DefaultType(tv.Type)) + return emitConv(fn, cmp, types.Default(tv.Type), e) default: panic("illegal op in BinaryExpr: " + e.Op.String()) } @@ -667,9 +629,8 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { High: high, Max: max, } - v.setPos(e.Lbrack) v.setType(tv.Type) - return fn.emit(v) + return fn.emit(v, e) case *ast.Ident: obj := fn.Pkg.info.Uses[e] @@ -678,17 +639,17 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case *types.Builtin: return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)} case *types.Nil: - return nilConst(tv.Type) + return emitConst(fn, nilConst(tv.Type)) } // Package-level func or var? if v := fn.Prog.packageLevelValue(obj); v != nil { if _, ok := obj.(*types.Var); ok { - return emitLoad(fn, v) // var (address) + return emitLoad(fn, v, e) // var (address) } return v // (func) } // Local var. - return emitLoad(fn, fn.lookup(obj, false)) // var (address) + return emitLoad(fn, fn.lookup(obj, false), e) // var (address) case *ast.SelectorExpr: sel, ok := fn.Pkg.info.Selections[e] @@ -700,7 +661,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case types.MethodExpr: // (*T).f or T.f, the method f from the method-set of type T. // The result is a "thunk". - return emitConv(fn, makeThunk(fn.Prog, sel), tv.Type) + return emitConv(fn, makeThunk(fn.Prog, sel), tv.Type, e) case types.MethodVal: // e.f where e is an expression and f is a method. @@ -709,26 +670,26 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { rt := recvType(obj) wantAddr := isPointer(rt) escaping := true - v := b.receiver(fn, e.X, wantAddr, escaping, sel) + v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) if isInterface(rt) { // If v has interface type I, // we must emit a check that v is non-nil. // We use: typeassert v.(I). - emitTypeAssert(fn, v, rt, token.NoPos) + emitTypeAssert(fn, v, rt, e) } c := &MakeClosure{ Fn: makeBound(fn.Prog, obj), Bindings: []Value{v}, } - c.setPos(e.Sel.Pos()) + c.source = e.Sel c.setType(tv.Type) - return fn.emit(c) + return fn.emit(c, e) case types.FieldVal: indices := sel.Index() last := len(indices) - 1 v := b.expr(fn, e.X) - v = emitImplicitSelections(fn, v, indices[:last]) + v = emitImplicitSelections(fn, v, indices[:last], e) v = emitFieldSelection(fn, v, indices[last], false, e.Sel) return v } @@ -741,36 +702,33 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { // Non-addressable array (in a register). v := &Index{ X: b.expr(fn, e.X), - Index: emitConv(fn, b.expr(fn, e.Index), tInt), + Index: emitConv(fn, b.expr(fn, e.Index), tInt, e.Index), } - v.setPos(e.Lbrack) v.setType(t.Elem()) - return fn.emit(v) + return fn.emit(v, e) case *types.Map: // Maps are not addressable. mapt := fn.Pkg.typeOf(e.X).Underlying().(*types.Map) - v := &Lookup{ + v := &MapLookup{ X: b.expr(fn, e.X), - Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key()), + Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e.Index), } - v.setPos(e.Lbrack) v.setType(mapt.Elem()) - return fn.emit(v) + return fn.emit(v, e) case *types.Basic: // => string // Strings are not addressable. - v := &Lookup{ + v := &StringLookup{ X: b.expr(fn, e.X), Index: b.expr(fn, e.Index), } - v.setPos(e.Lbrack) v.setType(tByte) - return fn.emit(v) + return fn.emit(v, e) case *types.Slice, *types.Pointer: // *array // Addressable slice/array; use IndexAddr and Load. - return b.addr(fn, e, false).load(fn) + return b.addr(fn, e, false).load(fn, e) default: panic("unexpected container type in IndexExpr: " + t.String()) @@ -778,7 +736,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case *ast.CompositeLit, *ast.StarExpr: // Addressable types (lvalues) - return b.addr(fn, e, false).load(fn) + return b.addr(fn, e, false).load(fn, e) } panic(fmt.Sprintf("unexpected expr: %T", e)) @@ -802,7 +760,7 @@ func (b *builder) stmtList(fn *Function, list []ast.Stmt) { // // escaping is defined as per builder.addr(). // -func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *types.Selection) Value { +func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *types.Selection, source ast.Node) Value { var v Value if wantAddr && !sel.Indirect() && !isPointer(fn.Pkg.typeOf(e)) { v = b.addr(fn, e, escaping).address(fn) @@ -811,9 +769,9 @@ func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, se } last := len(sel.Index()) - 1 - v = emitImplicitSelections(fn, v, sel.Index()[:last]) + v = emitImplicitSelections(fn, v, sel.Index()[:last], source) if !wantAddr && isPointer(v.Type()) { - v = emitLoad(fn, v) + v = emitLoad(fn, v, e) } return v } @@ -823,8 +781,6 @@ func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, se // occurring in e. // func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { - c.pos = e.Lparen - // Is this a method call? if selector, ok := unparen(e.Fun).(*ast.SelectorExpr); ok { sel, ok := fn.Pkg.info.Selections[selector] @@ -833,7 +789,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { recv := recvType(obj) wantAddr := isPointer(recv) escaping := true - v := b.receiver(fn, selector.X, wantAddr, escaping, sel) + v := b.receiver(fn, selector.X, wantAddr, escaping, sel, selector) if isInterface(recv) { // Invoke-mode call. c.Value = v @@ -889,7 +845,7 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx // f(x, y, z...): pass slice z straight through. if e.Ellipsis != 0 { for i, arg := range e.Args { - v := emitConv(fn, b.expr(fn, arg), sig.Params().At(i).Type()) + v := emitConv(fn, b.expr(fn, arg), sig.Params().At(i).Type(), arg) args = append(args, v) } return args @@ -906,7 +862,7 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx v := b.expr(fn, arg) if ttuple, ok := v.Type().(*types.Tuple); ok { // MRV chain for i, n := 0, ttuple.Len(); i < n; i++ { - args = append(args, emitExtract(fn, v, i)) + args = append(args, emitExtract(fn, v, i, arg)) } } else { args = append(args, v) @@ -919,7 +875,7 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx np-- } for i := 0; i < np; i++ { - args[offset+i] = emitConv(fn, args[offset+i], sig.Params().At(i).Type()) + args[offset+i] = emitConv(fn, args[offset+i], sig.Params().At(i).Type(), args[offset+i].Source()) } // Actual->formal assignability conversions for variadic parameter, @@ -929,25 +885,24 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx st := sig.Params().At(np).Type().(*types.Slice) vt := st.Elem() if len(varargs) == 0 { - args = append(args, nilConst(st)) + args = append(args, emitConst(fn, nilConst(st))) } else { // Replace a suffix of args with a slice containing it. at := types.NewArray(vt, int64(len(varargs))) - a := emitNew(fn, at, token.NoPos) - a.setPos(e.Rparen) - a.Comment = "varargs" + a := emitNew(fn, at, e) + a.source = e for i, arg := range varargs { iaddr := &IndexAddr{ X: a, - Index: intConst(int64(i)), + Index: emitConst(fn, intConst(int64(i))), } iaddr.setType(types.NewPointer(vt)) - fn.emit(iaddr) - emitStore(fn, iaddr, arg, arg.Pos()) + fn.emit(iaddr, e) + emitStore(fn, iaddr, arg, arg.Source()) } s := &Slice{X: a} s.setType(st) - args[offset+np] = fn.emit(s) + args[offset+np] = fn.emit(s, args[offset+np].Source()) args = args[:offset+np+1] } } @@ -970,9 +925,9 @@ func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) { } // assignOp emits to fn code to perform loc = val. -func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, pos token.Pos) { - oldv := loc.load(fn) - loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, val, oldv.Type()), loc.typ(), pos)) +func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, source ast.Node) { + oldv := loc.load(fn, source) + loc.store(fn, emitArith(fn, op, oldv, emitConv(fn, val, oldv.Type(), source), loc.typ(), source), source) } // localValueSpec emits to fn code to define all of the vars in the @@ -988,7 +943,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { fn.addLocalForIdent(id) } lval := b.addr(fn, id, false) // non-escaping - b.assign(fn, lval, spec.Values[i], true, nil) + b.assign(fn, lval, spec.Values[i], true, nil, spec) } case len(spec.Values) == 0: @@ -1010,7 +965,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { if !isBlankIdent(id) { fn.addLocalForIdent(id) lhs := b.addr(fn, id, false) // non-escaping - lhs.store(fn, emitExtract(fn, tuple, i)) + lhs.store(fn, emitExtract(fn, tuple, i, id), id) } } } @@ -1021,7 +976,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { // // Note the similarity with localValueSpec. // -func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool) { +func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool, source ast.Node) { // Side effects of all LHSs and RHSs must occur in left-to-right order. lvals := make([]lvalue, len(lhss)) isZero := make([]bool, len(lhss)) @@ -1030,7 +985,7 @@ func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool) { if !isBlankIdent(lhs) { if isDef { if obj := fn.Pkg.info.Defs[lhs.(*ast.Ident)]; obj != nil { - fn.addNamedLocal(obj) + fn.addNamedLocal(obj, lhs) isZero[i] = true } } @@ -1047,7 +1002,7 @@ func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool) { // so we need a storebuf. var sb storebuf for i := range rhss { - b.assign(fn, lvals[i], rhss[i], isZero[i], &sb) + b.assign(fn, lvals[i], rhss[i], isZero[i], &sb, source) } sb.emit(fn) } else { @@ -1055,7 +1010,7 @@ func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool) { tuple := b.exprN(fn, rhss[0]) emitDebugRef(fn, rhss[0], tuple, false) for i, lval := range lvals { - lval.store(fn, emitExtract(fn, tuple, i)) + lval.store(fn, emitExtract(fn, tuple, i, source), source) } } } @@ -1102,20 +1057,17 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero case *types.Struct: if !isZero && len(e.Elts) != t.NumFields() { // memclear - sb.store(&address{addr, e.Lbrace, nil}, - zeroValue(fn, deref(addr.Type()))) + sb.store(&address{addr, nil}, zeroValue(fn, deref(addr.Type()), e), e) isZero = true } for i, e := range e.Elts { fieldIndex := i - pos := e.Pos() if kv, ok := e.(*ast.KeyValueExpr); ok { fname := kv.Key.(*ast.Ident).Name for i, n := 0, t.NumFields(); i < n; i++ { sf := t.Field(i) if sf.Name() == fname { fieldIndex = i - pos = kv.Colon e = kv.Value break } @@ -1127,8 +1079,8 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero Field: fieldIndex, } faddr.setType(types.NewPointer(sf.Type())) - fn.emit(faddr) - b.assign(fn, &address{addr: faddr, pos: pos, expr: e}, e, isZero, sb) + fn.emit(faddr, e) + b.assign(fn, &address{addr: faddr, expr: e}, e, isZero, sb, e) } case *types.Array, *types.Slice: @@ -1137,8 +1089,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero switch t := t.(type) { case *types.Slice: at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts)) - alloc := emitNew(fn, at, e.Lbrace) - alloc.Comment = "slicelit" + alloc := emitNew(fn, at, e) array = alloc case *types.Array: at = t @@ -1146,51 +1097,46 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero if !isZero && int64(len(e.Elts)) != at.Len() { // memclear - sb.store(&address{array, e.Lbrace, nil}, - zeroValue(fn, deref(array.Type()))) + sb.store(&address{array, nil}, zeroValue(fn, deref(array.Type()), e), e) } } var idx *Const for _, e := range e.Elts { - pos := e.Pos() if kv, ok := e.(*ast.KeyValueExpr); ok { idx = b.expr(fn, kv.Key).(*Const) - pos = kv.Colon e = kv.Value } else { var idxval int64 if idx != nil { idxval = idx.Int64() + 1 } - idx = intConst(idxval) + idx = emitConst(fn, intConst(idxval)) } iaddr := &IndexAddr{ X: array, Index: idx, } iaddr.setType(types.NewPointer(at.Elem())) - fn.emit(iaddr) + fn.emit(iaddr, e) if t != at { // slice // backing array is unaliased => storebuf not needed. - b.assign(fn, &address{addr: iaddr, pos: pos, expr: e}, e, true, nil) + b.assign(fn, &address{addr: iaddr, expr: e}, e, true, nil, e) } else { - b.assign(fn, &address{addr: iaddr, pos: pos, expr: e}, e, true, sb) + b.assign(fn, &address{addr: iaddr, expr: e}, e, true, sb, e) } } if t != at { // slice s := &Slice{X: array} - s.setPos(e.Lbrace) s.setType(typ) - sb.store(&address{addr: addr, pos: e.Lbrace, expr: e}, fn.emit(s)) + sb.store(&address{addr: addr, expr: e}, fn.emit(s, e), e) } case *types.Map: - m := &MakeMap{Reserve: intConst(int64(len(e.Elts)))} - m.setPos(e.Lbrace) + m := &MakeMap{Reserve: emitConst(fn, intConst(int64(len(e.Elts))))} m.setType(typ) - fn.emit(m) + fn.emit(m, e) for _, e := range e.Elts { e := e.(*ast.KeyValueExpr) @@ -1211,10 +1157,9 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero } loc := element{ - m: m, - k: emitConv(fn, key, t.Key()), - t: t.Elem(), - pos: e.Colon, + m: m, + k: emitConv(fn, key, t.Key(), e), + t: t.Elem(), } // We call assign() only because it takes care @@ -1223,29 +1168,142 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero // map[int]*struct{}{0: {}} implies &struct{}{}. // In-place update is of course impossible, // and no storebuf is needed. - b.assign(fn, &loc, e.Value, true, nil) + b.assign(fn, &loc, e.Value, true, nil, e) } - sb.store(&address{addr: addr, pos: e.Lbrace, expr: e}, m) + sb.store(&address{addr: addr, expr: e}, m, e) default: panic("unexpected CompositeLit type: " + t.String()) } } +func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { + if s.Tag == nil { + b.switchStmtDynamic(fn, s, label) + return + } + dynamic := false + for _, iclause := range s.Body.List { + clause := iclause.(*ast.CaseClause) + for _, cond := range clause.List { + if fn.Pkg.info.Types[unparen(cond)].Value == nil { + dynamic = true + break + } + } + } + + if dynamic { + b.switchStmtDynamic(fn, s, label) + return + } + + if s.Init != nil { + b.stmt(fn, s.Init) + } + + entry := fn.currentBlock + tag := b.expr(fn, s.Tag) + + heads := make([]*BasicBlock, 0, len(s.Body.List)) + bodies := make([]*BasicBlock, len(s.Body.List)) + conds := make([]Value, 0, len(s.Body.List)) + + hasDefault := false + done := fn.newBasicBlock(fmt.Sprintf("switch.done")) + if label != nil { + label._break = done + } + for i, stmt := range s.Body.List { + body := fn.newBasicBlock(fmt.Sprintf("switch.body.%d", i)) + bodies[i] = body + cas := stmt.(*ast.CaseClause) + if cas.List == nil { + // default branch + hasDefault = true + head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d", i)) + conds = append(conds, nil) + heads = append(heads, head) + fn.currentBlock = head + emitJump(fn, body, cas) + } + for j, cond := range stmt.(*ast.CaseClause).List { + fn.currentBlock = entry + head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d.%d", i, j)) + conds = append(conds, b.expr(fn, cond)) + heads = append(heads, head) + fn.currentBlock = head + emitJump(fn, body, cond) + } + } + + for i, stmt := range s.Body.List { + clause := stmt.(*ast.CaseClause) + body := bodies[i] + fn.currentBlock = body + fallthru := done + if i+1 < len(bodies) { + fallthru = bodies[i+1] + } + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _fallthrough: fallthru, + } + b.stmtList(fn, clause.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, stmt) + } + + if !hasDefault { + head := fn.newBasicBlock(fmt.Sprintf("switch.head.implicit-default")) + body := fn.newBasicBlock("switch.body.implicit-default") + fn.currentBlock = head + emitJump(fn, body, s) + fn.currentBlock = body + emitJump(fn, done, s) + heads = append(heads, head) + conds = append(conds, nil) + } + + if len(heads) != len(conds) { + panic(fmt.Sprintf("internal error: %d heads for %d conds", len(heads), len(conds))) + } + for _, head := range heads { + addEdge(entry, head) + } + fn.currentBlock = entry + entry.emit(&ConstantSwitch{ + Tag: tag, + Conds: conds, + }, s) + fn.currentBlock = done +} + // switchStmt emits to fn code for the switch statement s, optionally // labelled by label. // -func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { +func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblock) { // We treat SwitchStmt like a sequential if-else chain. - // Multiway dispatch can be recovered later by ssautil.Switches() + // Multiway dispatch can be recovered later by irutil.Switches() // to those cases that are free of side effects. if s.Init != nil { b.stmt(fn, s.Init) } - var tag Value = vTrue + kTrue := emitConst(fn, NewConst(constant.MakeBool(true), tBool)) + + var tagv Value = kTrue + var tagSource ast.Node = s if s.Tag != nil { - tag = b.expr(fn, s.Tag) + tagv = b.expr(fn, s.Tag) + tagSource = s.Tag } + // lifting only considers loads and stores, but we want different + // sigma nodes for the different comparisons. use a temporary and + // load it in every branch. + tag := fn.addLocal(tagv.Type(), tagSource) + emitStore(fn, tag, tagv, tagSource) + done := fn.newBasicBlock("switch.done") if label != nil { label._break = done @@ -1283,13 +1341,23 @@ func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { var nextCond *BasicBlock for _, cond := range cc.List { nextCond = fn.newBasicBlock("switch.next") - // TODO(adonovan): opt: when tag==vTrue, we'd - // get better code if we use b.cond(cond) - // instead of BinOp(EQL, tag, b.expr(cond)) - // followed by If. Don't forget conversions - // though. - cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos()) - emitIf(fn, cond, body, nextCond) + if tagv == kTrue { + // emit a proper if/else chain instead of a comparison + // of a value against true. + // + // NOTE(dh): adonovan had a todo saying "don't forget + // conversions though". As far as I can tell, there + // aren't any conversions that we need to take care of + // here. `case bool(a) && bool(b)` as well as `case + // bool(a && b)` are being taken care of by b.cond, + // and `case a` where a is not of type bool is + // invalid. + b.cond(fn, cond, body, nextCond) + } else { + cond := emitCompare(fn, token.EQL, emitLoad(fn, tag, cond), b.expr(fn, cond), cond) + emitIf(fn, cond, body, nextCond, cond.Source()) + } + fn.currentBlock = nextCond } fn.currentBlock = body @@ -1300,11 +1368,14 @@ func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { } b.stmtList(fn, cc.Body) fn.targets = fn.targets.tail - emitJump(fn, done) + emitJump(fn, done, s) fn.currentBlock = nextCond } if dfltBlock != nil { - emitJump(fn, dfltBlock) + // The lack of a Source for the jump doesn't matter, block + // fusing will get rid of the jump later. + + emitJump(fn, dfltBlock, s) fn.currentBlock = dfltBlock fn.targets = &targets{ tail: fn.targets, @@ -1314,138 +1385,175 @@ func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { b.stmtList(fn, *dfltBody) fn.targets = fn.targets.tail } - emitJump(fn, done) + emitJump(fn, done, s) fn.currentBlock = done } -// typeSwitchStmt emits to fn code for the type switch statement s, optionally -// labelled by label. -// func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lblock) { - // We treat TypeSwitchStmt like a sequential if-else chain. - // Multiway dispatch can be recovered later by ssautil.Switches(). - - // Typeswitch lowering: - // - // var x X - // switch y := x.(type) { - // case T1, T2: S1 // >1 (y := x) - // case nil: SN // nil (y := x) - // default: SD // 0 types (y := x) - // case T3: S3 // 1 type (y := x.(T3)) - // } - // - // ...s.Init... - // x := eval x - // .caseT1: - // t1, ok1 := typeswitch,ok x - // if ok1 then goto S1 else goto .caseT2 - // .caseT2: - // t2, ok2 := typeswitch,ok x - // if ok2 then goto S1 else goto .caseNil - // .S1: - // y := x - // ...S1... - // goto done - // .caseNil: - // if t2, ok2 := typeswitch,ok x - // if x == nil then goto SN else goto .caseT3 - // .SN: - // y := x - // ...SN... - // goto done - // .caseT3: - // t3, ok3 := typeswitch,ok x - // if ok3 then goto S3 else goto default - // .S3: - // y := t3 - // ...S3... - // goto done - // .default: - // y := x - // ...SD... - // goto done - // .done: - if s.Init != nil { b.stmt(fn, s.Init) } - var x Value - switch ass := s.Assign.(type) { + var tag Value + switch e := s.Assign.(type) { case *ast.ExprStmt: // x.(type) - x = b.expr(fn, unparen(ass.X).(*ast.TypeAssertExpr).X) + tag = b.expr(fn, unparen(e.X).(*ast.TypeAssertExpr).X) case *ast.AssignStmt: // y := x.(type) - x = b.expr(fn, unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X) + tag = b.expr(fn, unparen(e.Rhs[0]).(*ast.TypeAssertExpr).X) + default: + panic("unreachable") } + tagPtr := fn.addLocal(tag.Type(), tag.Source()) + emitStore(fn, tagPtr, tag, tag.Source()) - done := fn.newBasicBlock("typeswitch.done") + // +1 in case there's no explicit default case + heads := make([]*BasicBlock, 0, len(s.Body.List)+1) + + entry := fn.currentBlock + done := fn.newBasicBlock("done") if label != nil { label._break = done } + + // set up type switch and constant switch, populate their conditions + tswtch := &TypeSwitch{ + Tag: emitLoad(fn, tagPtr, tag.Source()), + Conds: make([]types.Type, 0, len(s.Body.List)+1), + } + cswtch := &ConstantSwitch{ + Conds: make([]Value, 0, len(s.Body.List)+1), + } + + rets := make([]types.Type, 0, len(s.Body.List)+1) + index := 0 var default_ *ast.CaseClause for _, clause := range s.Body.List { cc := clause.(*ast.CaseClause) + if obj := fn.Pkg.info.Implicits[cc]; obj != nil { + fn.addNamedLocal(obj, cc) + } if cc.List == nil { + // default case default_ = cc + } else { + for _, expr := range cc.List { + tswtch.Conds = append(tswtch.Conds, fn.Pkg.typeOf(expr)) + cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(index)))) + index++ + } + if len(cc.List) == 1 { + rets = append(rets, fn.Pkg.typeOf(cc.List[0])) + } else { + for range cc.List { + rets = append(rets, tag.Type()) + } + } + } + } + + // default branch + rets = append(rets, tag.Type()) + + var vars []*types.Var + vars = append(vars, varIndex) + for _, typ := range rets { + vars = append(vars, anonVar(typ)) + } + tswtch.setType(types.NewTuple(vars...)) + // default branch + fn.currentBlock = entry + fn.emit(tswtch, s) + cswtch.Conds = append(cswtch.Conds, emitConst(fn, intConst(int64(-1)))) + // in theory we should add a local and stores/loads for tswtch, to + // generate sigma nodes in the branches. however, there isn't any + // useful information we could possibly attach to it. + cswtch.Tag = emitExtract(fn, tswtch, 0, s) + fn.emit(cswtch, s) + + // build heads and bodies + index = 0 + for _, clause := range s.Body.List { + cc := clause.(*ast.CaseClause) + if cc.List == nil { continue } + body := fn.newBasicBlock("typeswitch.body") - var next *BasicBlock - var casetype types.Type - var ti Value // ti, ok := typeassert,ok x - for _, cond := range cc.List { - next = fn.newBasicBlock("typeswitch.next") - casetype = fn.Pkg.typeOf(cond) - var condv Value - if casetype == tUntypedNil { - condv = emitCompare(fn, token.EQL, x, nilConst(x.Type()), token.NoPos) - ti = x - } else { - yok := emitTypeTest(fn, x, casetype, cc.Case) - ti = emitExtract(fn, yok, 0) - condv = emitExtract(fn, yok, 1) + for _, expr := range cc.List { + head := fn.newBasicBlock("typeswitch.head") + heads = append(heads, head) + fn.currentBlock = head + + if obj := fn.Pkg.info.Implicits[cc]; obj != nil { + // In a switch y := x.(type), each case clause + // implicitly declares a distinct object y. + // In a single-type case, y has that type. + // In multi-type cases, 'case nil' and default, + // y has the same type as the interface operand. + + l := fn.objects[obj] + if rets[index] == tUntypedNil { + emitStore(fn, l, emitConst(fn, nilConst(tswtch.Tag.Type())), s.Assign) + } else { + x := emitExtract(fn, tswtch, index+1, s.Assign) + emitStore(fn, l, x, nil) + } } - emitIf(fn, condv, body, next) - fn.currentBlock = next - } - if len(cc.List) != 1 { - ti = x + + emitJump(fn, body, expr) + index++ } fn.currentBlock = body - b.typeCaseBody(fn, cc, ti, done) - fn.currentBlock = next + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + b.stmtList(fn, cc.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, clause) } - if default_ != nil { - b.typeCaseBody(fn, default_, x, done) + + if default_ == nil { + // implicit default + heads = append(heads, done) } else { - emitJump(fn, done) + body := fn.newBasicBlock("typeswitch.default") + heads = append(heads, body) + fn.currentBlock = body + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + if obj := fn.Pkg.info.Implicits[default_]; obj != nil { + l := fn.objects[obj] + x := emitExtract(fn, tswtch, index+1, s.Assign) + emitStore(fn, l, x, s) + } + b.stmtList(fn, default_.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, s) + } + + fn.currentBlock = entry + for _, head := range heads { + addEdge(entry, head) } fn.currentBlock = done } -func (b *builder) typeCaseBody(fn *Function, cc *ast.CaseClause, x Value, done *BasicBlock) { - if obj := fn.Pkg.info.Implicits[cc]; obj != nil { - // In a switch y := x.(type), each case clause - // implicitly declares a distinct object y. - // In a single-type case, y has that type. - // In multi-type cases, 'case nil' and default, - // y has the same type as the interface operand. - emitStore(fn, fn.addNamedLocal(obj), x, obj.Pos()) - } - fn.targets = &targets{ - tail: fn.targets, - _break: done, - } - b.stmtList(fn, cc.Body) - fn.targets = fn.targets.tail - emitJump(fn, done) -} - // selectStmt emits to fn code for the select statement s, optionally // labelled by label. // -func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { +func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (noreturn bool) { + if len(s.Body.List) == 0 { + instr := &Select{Blocking: true} + instr.setType(types.NewTuple(varIndex, varOk)) + fn.emit(instr, s) + fn.emit(new(Unreachable), s) + addEdge(fn.currentBlock, fn.Exit) + return true + } + // A blocking select of a single case degenerates to a // simple send or receive. // TODO(adonovan): opt: is this optimization worth its weight? @@ -1463,9 +1571,9 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { } b.stmtList(fn, clause.Body) fn.targets = fn.targets.tail - emitJump(fn, done) + emitJump(fn, done, clause) fn.currentBlock = done - return + return false } } @@ -1487,7 +1595,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { Dir: types.SendOnly, Chan: ch, Send: emitConv(fn, b.expr(fn, comm.Value), - ch.Type().Underlying().(*types.Chan).Elem()), + ch.Type().Underlying().(*types.Chan).Elem(), comm), Pos: comm.Arrow, } if debugInfo { @@ -1520,22 +1628,12 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { } // We dispatch on the (fair) result of Select using a - // sequential if-else chain, in effect: - // - // idx, recvOk, r0...r_n-1 := select(...) - // if idx == 0 { // receive on channel 0 (first receive => r0) - // x, ok := r0, recvOk - // ...state0... - // } else if v == 1 { // send on channel 1 - // ...state1... - // } else { - // ...default... - // } + // switch on the returned index. sel := &Select{ States: states, Blocking: blocking, } - sel.setPos(s.Select) + sel.source = s var vars []*types.Var vars = append(vars, varIndex, varOk) for _, st := range states { @@ -1545,28 +1643,45 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { } } sel.setType(types.NewTuple(vars...)) - - fn.emit(sel) - idx := emitExtract(fn, sel, 0) + fn.emit(sel, s) + idx := emitExtract(fn, sel, 0, s) done := fn.newBasicBlock("select.done") if label != nil { label._break = done } - var defaultBody *[]ast.Stmt + entry := fn.currentBlock + swtch := &ConstantSwitch{ + Tag: idx, + // one condition per case + Conds: make([]Value, 0, len(s.Body.List)+1), + } + // note that we don't need heads; a select case can only have a single condition + var bodies []*BasicBlock + state := 0 r := 2 // index in 'sel' tuple of value; increments if st.Dir==RECV for _, cc := range s.Body.List { clause := cc.(*ast.CommClause) if clause.Comm == nil { - defaultBody = &clause.Body + body := fn.newBasicBlock("select.default") + fn.currentBlock = body + bodies = append(bodies, body) + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + b.stmtList(fn, clause.Body) + emitJump(fn, done, s) + fn.targets = fn.targets.tail + swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(-1))) continue } + swtch.Conds = append(swtch.Conds, emitConst(fn, intConst(int64(state)))) body := fn.newBasicBlock("select.body") - next := fn.newBasicBlock("select.next") - emitIf(fn, emitCompare(fn, token.EQL, idx, intConst(int64(state)), token.NoPos), body, next) fn.currentBlock = body + bodies = append(bodies, body) fn.targets = &targets{ tail: fn.targets, _break: done, @@ -1574,7 +1689,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { switch comm := clause.Comm.(type) { case *ast.ExprStmt: // <-ch if debugInfo { - v := emitExtract(fn, sel, r) + v := emitExtract(fn, sel, r, comm) emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false) } r++ @@ -1584,44 +1699,33 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { fn.addLocalForIdent(comm.Lhs[0].(*ast.Ident)) } x := b.addr(fn, comm.Lhs[0], false) // non-escaping - v := emitExtract(fn, sel, r) + v := emitExtract(fn, sel, r, comm) if debugInfo { emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false) } - x.store(fn, v) + x.store(fn, v, comm) if len(comm.Lhs) == 2 { // x, ok := ... if comm.Tok == token.DEFINE { fn.addLocalForIdent(comm.Lhs[1].(*ast.Ident)) } ok := b.addr(fn, comm.Lhs[1], false) // non-escaping - ok.store(fn, emitExtract(fn, sel, 1)) + ok.store(fn, emitExtract(fn, sel, 1, comm), comm) } r++ } b.stmtList(fn, clause.Body) fn.targets = fn.targets.tail - emitJump(fn, done) - fn.currentBlock = next + emitJump(fn, done, s) state++ } - if defaultBody != nil { - fn.targets = &targets{ - tail: fn.targets, - _break: done, - } - b.stmtList(fn, *defaultBody) - fn.targets = fn.targets.tail - } else { - // A blocking select must match some case. - // (This should really be a runtime.errorString, not a string.) - fn.emit(&Panic{ - X: emitConv(fn, stringConst("blocking select matched no case"), tEface), - }) - fn.currentBlock = fn.newBasicBlock("unreachable") + fn.currentBlock = entry + fn.emit(swtch, s) + for _, body := range bodies { + addEdge(entry, body) } - emitJump(fn, done) fn.currentBlock = done + return false } // forStmt emits to fn code for the for statement s, optionally @@ -1656,7 +1760,7 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { label._break = done label._continue = cont } - emitJump(fn, loop) + emitJump(fn, loop, s) fn.currentBlock = loop if loop != body { b.cond(fn, s.Cond, body, done) @@ -1669,12 +1773,12 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { } b.stmt(fn, s.Body) fn.targets = fn.targets.tail - emitJump(fn, cont) + emitJump(fn, cont, s) if s.Post != nil { fn.currentBlock = cont b.stmt(fn, s.Post) - emitJump(fn, loop) // back-edge + emitJump(fn, loop, s) // back-edge } fn.currentBlock = done } @@ -1684,7 +1788,7 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { // The v result is defined only if tv is non-nil. // forPos is the position of the "for" token. // -func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.Pos) (k, v Value, loop, done *BasicBlock) { +func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) { // // length = len(x) // index = -1 @@ -1707,37 +1811,37 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P // elimination if x is pure, static unrolling, etc. // Ranging over a nil *array may have >0 iterations. // We still generate code for x, in case it has effects. - length = intConst(arr.Len()) + length = emitConst(fn, intConst(arr.Len())) } else { // length = len(x). var c Call c.Call.Value = makeLen(x.Type()) c.Call.Args = []Value{x} c.setType(tInt) - length = fn.emit(&c) + length = fn.emit(&c, source) } - index := fn.addLocal(tInt, token.NoPos) - emitStore(fn, index, intConst(-1), pos) + index := fn.addLocal(tInt, source) + emitStore(fn, index, emitConst(fn, intConst(-1)), source) loop = fn.newBasicBlock("rangeindex.loop") - emitJump(fn, loop) + emitJump(fn, loop, source) fn.currentBlock = loop incr := &BinOp{ Op: token.ADD, - X: emitLoad(fn, index), - Y: vOne, + X: emitLoad(fn, index, source), + Y: emitConst(fn, intConst(1)), } incr.setType(tInt) - emitStore(fn, index, fn.emit(incr), pos) + emitStore(fn, index, fn.emit(incr, source), source) body := fn.newBasicBlock("rangeindex.body") done = fn.newBasicBlock("rangeindex.done") - emitIf(fn, emitCompare(fn, token.LSS, incr, length, token.NoPos), body, done) + emitIf(fn, emitCompare(fn, token.LSS, incr, length, source), body, done, source) fn.currentBlock = body - k = emitLoad(fn, index) + k = emitLoad(fn, index, source) if tv != nil { switch t := x.Type().Underlying().(type) { case *types.Array: @@ -1746,7 +1850,7 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P Index: k, } instr.setType(t.Elem()) - v = fn.emit(instr) + v = fn.emit(instr, source) case *types.Pointer: // *array instr := &IndexAddr{ @@ -1754,7 +1858,7 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P Index: k, } instr.setType(types.NewPointer(t.Elem().Underlying().(*types.Array).Elem())) - v = emitLoad(fn, fn.emit(instr)) + v = emitLoad(fn, fn.emit(instr, source), source) case *types.Slice: instr := &IndexAddr{ @@ -1762,7 +1866,7 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P Index: k, } instr.setType(types.NewPointer(t.Elem())) - v = emitLoad(fn, fn.emit(instr)) + v = emitLoad(fn, fn.emit(instr, source), source) default: panic("rangeIndexed x:" + t.String()) @@ -1776,7 +1880,7 @@ func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, pos token.P // tk and tv are the types of the key/value results k and v, or nil // if the respective component is not wanted. // -func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.Pos) (k, v Value, loop, done *BasicBlock) { +func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) { // // it = range x // loop: (target of continue) @@ -1799,12 +1903,11 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. } rng := &Range{X: x} - rng.setPos(pos) rng.setType(tRangeIter) - it := fn.emit(rng) + it := fn.emit(rng, source) loop = fn.newBasicBlock("rangeiter.loop") - emitJump(fn, loop) + emitJump(fn, loop, source) fn.currentBlock = loop _, isString := x.Type().Underlying().(*types.Basic) @@ -1818,18 +1921,18 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. newVar("k", tk), newVar("v", tv), )) - fn.emit(okv) + fn.emit(okv, source) body := fn.newBasicBlock("rangeiter.body") done = fn.newBasicBlock("rangeiter.done") - emitIf(fn, emitExtract(fn, okv, 0), body, done) + emitIf(fn, emitExtract(fn, okv, 0, source), body, done, source) fn.currentBlock = body if tk != tInvalid { - k = emitExtract(fn, okv, 1) + k = emitExtract(fn, okv, 1, source) } if tv != tInvalid { - v = emitExtract(fn, okv, 2) + v = emitExtract(fn, okv, 2, source) } return } @@ -1840,7 +1943,7 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. // not wanted // pos is the position of the '=' or ':=' token. // -func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos) (k Value, loop, done *BasicBlock) { +func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) { // // loop: (target of continue) // ko = <-x (key, ok) @@ -1853,25 +1956,15 @@ func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos) // done: (target of break) loop = fn.newBasicBlock("rangechan.loop") - emitJump(fn, loop) + emitJump(fn, loop, source) fn.currentBlock = loop - recv := &UnOp{ - Op: token.ARROW, - X: x, - CommaOk: true, - } - recv.setPos(pos) - recv.setType(types.NewTuple( - newVar("k", x.Type().Underlying().(*types.Chan).Elem()), - varOk, - )) - ko := fn.emit(recv) + retv := emitRecv(fn, x, true, types.NewTuple(newVar("k", x.Type().Underlying().(*types.Chan).Elem()), varOk), source) body := fn.newBasicBlock("rangechan.body") done = fn.newBasicBlock("rangechan.done") - emitIf(fn, emitExtract(fn, ko, 1), body, done) + emitIf(fn, emitExtract(fn, retv, 1, source), body, done, source) fn.currentBlock = body if tk != nil { - k = emitExtract(fn, ko, 0) + k = emitExtract(fn, retv, 0, source) } return } @@ -1879,7 +1972,7 @@ func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, pos token.Pos) // rangeStmt emits to fn code for the range statement s, optionally // labelled by label. // -func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { +func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, source ast.Node) { var tk, tv types.Type if s.Key != nil && !isBlankIdent(s.Key) { tk = fn.Pkg.typeOf(s.Key) @@ -1909,13 +2002,13 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { var loop, done *BasicBlock switch rt := x.Type().Underlying().(type) { case *types.Slice, *types.Array, *types.Pointer: // *array - k, v, loop, done = b.rangeIndexed(fn, x, tv, s.For) + k, v, loop, done = b.rangeIndexed(fn, x, tv, source) case *types.Chan: - k, loop, done = b.rangeChan(fn, x, tk, s.For) + k, loop, done = b.rangeChan(fn, x, tk, source) case *types.Map, *types.Basic: // string - k, v, loop, done = b.rangeIter(fn, x, tk, tv, s.For) + k, v, loop, done = b.rangeIter(fn, x, tk, tv, source) default: panic("Cannot range over: " + rt.String()) @@ -1930,10 +2023,10 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { vl = b.addr(fn, s.Value, false) // non-escaping } if tk != nil { - kl.store(fn, k) + kl.store(fn, k, s) } if tv != nil { - vl.store(fn, v) + vl.store(fn, v, s) } if label != nil { @@ -1948,11 +2041,11 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) { } b.stmt(fn, s.Body) fn.targets = fn.targets.tail - emitJump(fn, loop) // back-edge + emitJump(fn, loop, source) // back-edge fn.currentBlock = done } -// stmt lowers statement s to SSA form, emitting code to fn. +// stmt lowers statement s to IR form, emitting code to fn. func (b *builder) stmt(fn *Function, _s ast.Stmt) { // The label of the current statement. If non-nil, its _goto // target is always set; its _break and _continue are set only @@ -1976,7 +2069,7 @@ start: case *ast.LabeledStmt: label = fn.labelledBlock(s.Label) - emitJump(fn, label._goto) + emitJump(fn, label._goto, s) fn.currentBlock = label._goto _s = s.Stmt goto start // effectively: tailcall stmt(fn, s.Stmt, label) @@ -1985,12 +2078,12 @@ start: b.expr(fn, s.X) case *ast.SendStmt: - fn.emit(&Send{ + instr := &Send{ Chan: b.expr(fn, s.Chan), X: emitConv(fn, b.expr(fn, s.Value), - fn.Pkg.typeOf(s.Chan).Underlying().(*types.Chan).Elem()), - pos: s.Arrow, - }) + fn.Pkg.typeOf(s.Chan).Underlying().(*types.Chan).Elem(), s), + } + fn.emit(instr, s) case *ast.IncDecStmt: op := token.ADD @@ -1998,37 +2091,37 @@ start: op = token.SUB } loc := b.addr(fn, s.X, false) - b.assignOp(fn, loc, NewConst(constant.MakeInt64(1), loc.typ()), op, s.Pos()) + b.assignOp(fn, loc, emitConst(fn, NewConst(constant.MakeInt64(1), loc.typ())), op, s) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: - b.assignStmt(fn, s.Lhs, s.Rhs, s.Tok == token.DEFINE) + b.assignStmt(fn, s.Lhs, s.Rhs, s.Tok == token.DEFINE, _s) default: // +=, etc. op := s.Tok + token.ADD - token.ADD_ASSIGN - b.assignOp(fn, b.addr(fn, s.Lhs[0], false), b.expr(fn, s.Rhs[0]), op, s.Pos()) + b.assignOp(fn, b.addr(fn, s.Lhs[0], false), b.expr(fn, s.Rhs[0]), op, s) } case *ast.GoStmt: // The "intrinsics" new/make/len/cap are forbidden here. // panic is treated like an ordinary function call. - v := Go{pos: s.Go} + v := Go{} b.setCall(fn, s.Call, &v.Call) - fn.emit(&v) + fn.emit(&v, s) case *ast.DeferStmt: // The "intrinsics" new/make/len/cap are forbidden here. // panic is treated like an ordinary function call. - v := Defer{pos: s.Defer} + v := Defer{} b.setCall(fn, s.Call, &v.Call) - fn.emit(&v) - - // A deferred call can cause recovery from panic, - // and control resumes at the Recover block. - createRecoverBlock(fn) + fn.hasDefer = true + fn.emit(&v, s) case *ast.ReturnStmt: + // TODO(dh): we could emit tigher position information by + // using the ith returned expression + var results []Value if len(s.Results) == 1 && fn.Signature.Results().Len() > 1 { // Return of one expression in a multi-valued function. @@ -2036,34 +2129,23 @@ start: ttuple := tuple.Type().(*types.Tuple) for i, n := 0, ttuple.Len(); i < n; i++ { results = append(results, - emitConv(fn, emitExtract(fn, tuple, i), - fn.Signature.Results().At(i).Type())) + emitConv(fn, emitExtract(fn, tuple, i, s), + fn.Signature.Results().At(i).Type(), s)) } } else { // 1:1 return, or no-arg return in non-void function. for i, r := range s.Results { - v := emitConv(fn, b.expr(fn, r), fn.Signature.Results().At(i).Type()) + v := emitConv(fn, b.expr(fn, r), fn.Signature.Results().At(i).Type(), s) results = append(results, v) } } - if fn.namedResults != nil { - // Function has named result parameters (NRPs). - // Perform parallel assignment of return operands to NRPs. - for i, r := range results { - emitStore(fn, fn.namedResults[i], r, s.Return) - } + + ret := fn.results() + for i, r := range results { + emitStore(fn, ret[i], r, s) } - // Run function calls deferred in this - // function when explicitly returning from it. - fn.emit(new(RunDefers)) - if fn.namedResults != nil { - // Reload NRPs to form the result tuple. - results = results[:0] - for _, r := range fn.namedResults { - results = append(results, emitLoad(fn, r)) - } - } - fn.emit(&Return{Results: results, pos: s.Return}) + + emitJump(fn, fn.Exit, s) fn.currentBlock = fn.newBasicBlock("unreachable") case *ast.BranchStmt: @@ -2095,7 +2177,8 @@ start: case token.GOTO: block = fn.labelledBlock(s.Label)._goto } - emitJump(fn, block) + j := emitJump(fn, block, s) + j.Comment = s.Tok.String() fn.currentBlock = fn.newBasicBlock("unreachable") case *ast.BlockStmt: @@ -2111,15 +2194,16 @@ start: if s.Else != nil { els = fn.newBasicBlock("if.else") } - b.cond(fn, s.Cond, then, els) + instr := b.cond(fn, s.Cond, then, els) + instr.source = s fn.currentBlock = then b.stmt(fn, s.Body) - emitJump(fn, done) + emitJump(fn, done, s) if s.Else != nil { fn.currentBlock = els b.stmt(fn, s.Else) - emitJump(fn, done) + emitJump(fn, done, s) } fn.currentBlock = done @@ -2131,20 +2215,23 @@ start: b.typeSwitchStmt(fn, s, label) case *ast.SelectStmt: - b.selectStmt(fn, s, label) + if b.selectStmt(fn, s, label) { + // the select has no cases, it blocks forever + fn.currentBlock = fn.newBasicBlock("unreachable") + } case *ast.ForStmt: b.forStmt(fn, s, label) case *ast.RangeStmt: - b.rangeStmt(fn, s, label) + b.rangeStmt(fn, s, label, s) default: panic(fmt.Sprintf("unexpected statement kind: %T", s)) } } -// buildFunction builds SSA code for the body of function fn. Idempotent. +// buildFunction builds IR code for the body of function fn. Idempotent. func (b *builder) buildFunction(fn *Function) { if fn.Blocks != nil { return // building already started @@ -2153,7 +2240,7 @@ func (b *builder) buildFunction(fn *Function) { var recvField *ast.FieldList var body *ast.BlockStmt var functype *ast.FuncType - switch n := fn.syntax.(type) { + switch n := fn.source.(type) { case nil: return // not a Go source function. (Synthetic, or from object file.) case *ast.FuncDecl: @@ -2167,6 +2254,16 @@ func (b *builder) buildFunction(fn *Function) { panic(n) } + if fn.Package().Pkg.Path() == "syscall" && fn.Name() == "Exit" { + // syscall.Exit is a stub and the way os.Exit terminates the + // process. Note that there are other functions in the runtime + // that also terminate or unwind that we cannot analyze. + // However, they aren't stubs, so buildExits ends up getting + // called on them, so that's where we handle those special + // cases. + fn.WillExit = true + } + if body == nil { // External function. if fn.Params == nil { @@ -2178,22 +2275,26 @@ func (b *builder) buildFunction(fn *Function) { // We set Function.Params even though there is no body // code to reference them. This simplifies clients. if recv := fn.Signature.Recv(); recv != nil { - fn.addParamObj(recv) + // XXX synthesize an ast.Node + fn.addParamObj(recv, nil) } params := fn.Signature.Params() for i, n := 0, params.Len(); i < n; i++ { - fn.addParamObj(params.At(i)) + // XXX synthesize an ast.Node + fn.addParamObj(params.At(i), nil) } } return } if fn.Prog.mode&LogSource != 0 { - defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))() + defer logStack("build function %s @ %s", fn, fn.Prog.Fset.Position(fn.Pos()))() } + fn.blocksets = b.blocksets fn.startBody() fn.createSyntacticParams(recvField, functype) + fn.exitBlock() b.stmt(fn, body) - if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) { + if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb.Preds != nil) { // Control fell off the end of the function's body block. // // Block optimizations eliminate the current block, if @@ -2201,13 +2302,20 @@ func (b *builder) buildFunction(fn *Function) { // if this no-arg return is ill-typed for // fn.Signature.Results, this block must be // unreachable. The sanity checker checks this. - fn.emit(new(RunDefers)) - fn.emit(new(Return)) + // fn.emit(new(RunDefers)) + // fn.emit(new(Return)) + emitJump(fn, fn.Exit, nil) } + optimizeBlocks(fn) + buildFakeExits(fn) + b.buildExits(fn) + b.addUnreachables(fn) fn.finishBody() + b.blocksets = fn.blocksets + fn.functionBody = nil } -// buildFuncDecl builds SSA code for the function or method declared +// buildFuncDecl builds IR code for the function or method declared // by decl in package pkg. // func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) { @@ -2220,13 +2328,13 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) { var v Call v.Call.Value = fn v.setType(types.NewTuple()) - pkg.init.emit(&v) + pkg.init.emit(&v, decl) } + fn.source = decl b.buildFunction(fn) } // Build calls Package.Build for each package in prog. -// Building occurs in parallel unless the BuildSerially mode flag was set. // // Build is intended for whole-program analysis; a typical compiler // need only build a single package. @@ -2234,22 +2342,12 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) { // Build is idempotent and thread-safe. // func (prog *Program) Build() { - var wg sync.WaitGroup for _, p := range prog.packages { - if prog.mode&BuildSerially != 0 { - p.Build() - } else { - wg.Add(1) - go func(p *Package) { - p.Build() - wg.Done() - }(p) - } + p.Build() } - wg.Wait() } -// Build builds SSA code for all functions and vars in package p. +// Build builds IR code for all functions and vars in package p. // // Precondition: CreatePackage must have been called for all of p's // direct imports (and hence its direct imports must have been @@ -2277,33 +2375,33 @@ func (p *Package) build() { } init := p.init init.startBody() + init.exitBlock() var done *BasicBlock - if p.Prog.mode&BareInits == 0 { - // Make init() skip if package is already initialized. - initguard := p.Var("init$guard") - doinit := init.newBasicBlock("init.start") - done = init.newBasicBlock("init.done") - emitIf(init, emitLoad(init, initguard), done, doinit) - init.currentBlock = doinit - emitStore(init, initguard, vTrue, token.NoPos) + // Make init() skip if package is already initialized. + initguard := p.Var("init$guard") + doinit := init.newBasicBlock("init.start") + done = init.Exit + emitIf(init, emitLoad(init, initguard, nil), done, doinit, nil) + init.currentBlock = doinit + emitStore(init, initguard, emitConst(init, NewConst(constant.MakeBool(true), tBool)), nil) - // Call the init() function of each package we import. - for _, pkg := range p.Pkg.Imports() { - prereq := p.Prog.packages[pkg] - if prereq == nil { - panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path())) - } - var v Call - v.Call.Value = prereq.init - v.Call.pos = init.pos - v.setType(types.NewTuple()) - init.emit(&v) + // Call the init() function of each package we import. + for _, pkg := range p.Pkg.Imports() { + prereq := p.Prog.packages[pkg] + if prereq == nil { + panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path())) } + var v Call + v.Call.Value = prereq.init + v.setType(types.NewTuple()) + init.emit(&v, nil) } - var b builder + b := builder{ + printFunc: p.printFunc, + } // Initialize package-level vars in correct order. for _, varinit := range p.info.InitOrder { @@ -2315,11 +2413,12 @@ func (p *Package) build() { // 1:1 initialization: var x, y = a(), b() var lval lvalue if v := varinit.Lhs[0]; v.Name() != "_" { - lval = &address{addr: p.values[v].(*Global), pos: v.Pos()} + lval = &address{addr: p.values[v].(*Global)} } else { lval = blank{} } - b.assign(init, lval, varinit.Rhs, true, nil) + // TODO(dh): do emit position information + b.assign(init, lval, varinit.Rhs, true, nil, nil) } else { // n:1 initialization: var x, y := f() tuple := b.exprN(init, varinit.Rhs) @@ -2327,7 +2426,7 @@ func (p *Package) build() { if v.Name() == "_" { continue } - emitStore(init, p.values[v].(*Global), emitExtract(init, tuple, i), v.Pos()) + emitStore(init, p.values[v].(*Global), emitExtract(init, tuple, i, nil), nil) } } } @@ -2344,11 +2443,7 @@ func (p *Package) build() { } // Finish up init(). - if p.Prog.mode&BareInits == 0 { - emitJump(init, done) - init.currentBlock = done - } - init.emit(new(Return)) + emitJump(init, done, nil) init.finishBody() p.info = nil // We no longer need ASTs or go/types deductions. diff --git a/vendor/honnef.co/go/tools/ssa/const.go b/vendor/honnef.co/go/tools/ir/const.go similarity index 85% rename from vendor/honnef.co/go/tools/ssa/const.go rename to vendor/honnef.co/go/tools/ir/const.go index f95d9e1140..7cdf006e83 100644 --- a/vendor/honnef.co/go/tools/ssa/const.go +++ b/vendor/honnef.co/go/tools/ir/const.go @@ -2,14 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines the Const SSA value type. import ( "fmt" "go/constant" - "go/token" "go/types" "strconv" ) @@ -18,7 +17,12 @@ import ( // val must be valid according to the specification of Const.Value. // func NewConst(val constant.Value, typ types.Type) *Const { - return &Const{typ, val} + return &Const{ + register: register{ + typ: typ, + }, + Value: val, + } } // intConst returns an 'int' constant that evaluates to i. @@ -71,43 +75,25 @@ func zeroConst(t types.Type) *Const { } func (c *Const) RelString(from *types.Package) string { - var s string + var p string if c.Value == nil { - s = "nil" + p = "nil" } else if c.Value.Kind() == constant.String { - s = constant.StringVal(c.Value) + v := constant.StringVal(c.Value) const max = 20 // TODO(adonovan): don't cut a rune in half. - if len(s) > max { - s = s[:max-3] + "..." // abbreviate + if len(v) > max { + v = v[:max-3] + "..." // abbreviate } - s = strconv.Quote(s) + p = strconv.Quote(v) } else { - s = c.Value.String() + p = c.Value.String() } - return s + ":" + relType(c.Type(), from) -} - -func (c *Const) Name() string { - return c.RelString(nil) + return fmt.Sprintf("Const <%s> {%s}", relType(c.Type(), from), p) } func (c *Const) String() string { - return c.Name() -} - -func (c *Const) Type() types.Type { - return c.typ -} - -func (c *Const) Referrers() *[]Instruction { - return nil -} - -func (c *Const) Parent() *Function { return nil } - -func (c *Const) Pos() token.Pos { - return token.NoPos + return c.RelString(c.Parent().pkg()) } // IsNil returns true if this constant represents a typed or untyped nil value. @@ -115,8 +101,6 @@ func (c *Const) IsNil() bool { return c.Value == nil } -// TODO(adonovan): move everything below into honnef.co/go/tools/ssa/interp. - // Int64 returns the numeric value of this constant truncated to fit // a signed 64-bit integer. // diff --git a/vendor/honnef.co/go/tools/ssa/create.go b/vendor/honnef.co/go/tools/ir/create.go similarity index 82% rename from vendor/honnef.co/go/tools/ssa/create.go rename to vendor/honnef.co/go/tools/ir/create.go index 85163a0c5a..ff81a244bd 100644 --- a/vendor/honnef.co/go/tools/ssa/create.go +++ b/vendor/honnef.co/go/tools/ir/create.go @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir -// This file implements the CREATE phase of SSA construction. +// This file implements the CREATE phase of IR construction. // See builder.go for explanation. import ( @@ -18,9 +18,9 @@ import ( "golang.org/x/tools/go/types/typeutil" ) -// NewProgram returns a new SSA Program. +// NewProgram returns a new IR Program. // -// mode controls diagnostics and checking during SSA construction. +// mode controls diagnostics and checking during IR construction. // func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { prog := &Program{ @@ -75,7 +75,6 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { name: name, object: obj, typ: types.NewPointer(obj.Type()), // address - pos: obj.Pos(), } pkg.values[obj] = g pkg.Members[name] = g @@ -90,16 +89,20 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { name: name, object: obj, Signature: sig, - syntax: syntax, - pos: obj.Pos(), Pkg: pkg, Prog: pkg.Prog, } + + fn.source = syntax + fn.initHTML(pkg.printFunc) if syntax == nil { fn.Synthetic = "loaded from gc object file" + } else { + fn.functionBody = new(functionBody) } pkg.values[obj] = fn + pkg.Functions = append(pkg.Functions, fn) if sig.Recv() == nil { pkg.Members[name] = fn // package-level function } @@ -152,35 +155,39 @@ func membersFromDecl(pkg *Package, decl ast.Decl) { } } -// CreatePackage constructs and returns an SSA Package from the +// CreatePackage constructs and returns an IR Package from the // specified type-checked, error-free file ASTs, and populates its // Members mapping. // // importable determines whether this package should be returned by a // subsequent call to ImportedPackage(pkg.Path()). // -// The real work of building SSA form for each function is not done +// The real work of building IR form for each function is not done // until a subsequent call to Package.Build(). // func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { p := &Package{ - Prog: prog, - Members: make(map[string]Member), - values: make(map[types.Object]Value), - Pkg: pkg, - info: info, // transient (CREATE and BUILD phases) - files: files, // transient (CREATE and BUILD phases) + Prog: prog, + Members: make(map[string]Member), + values: make(map[types.Object]Value), + Pkg: pkg, + info: info, // transient (CREATE and BUILD phases) + files: files, // transient (CREATE and BUILD phases) + printFunc: prog.PrintFunc, } // Add init() function. p.init = &Function{ - name: "init", - Signature: new(types.Signature), - Synthetic: "package initializer", - Pkg: p, - Prog: prog, + name: "init", + Signature: new(types.Signature), + Synthetic: "package initializer", + Pkg: p, + Prog: prog, + functionBody: new(functionBody), } + p.init.initHTML(prog.PrintFunc) p.Members[p.init.name] = p.init + p.Functions = append(p.Functions, p.init) // CREATE phase. // Allocate all package members: vars, funcs, consts and types. @@ -209,15 +216,13 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info * } } - if prog.mode&BareInits == 0 { - // Add initializer guard variable. - initguard := &Global{ - Pkg: p, - name: "init$guard", - typ: types.NewPointer(tBool), - } - p.Members[initguard.Name()] = initguard + // Add initializer guard variable. + initguard := &Global{ + Pkg: p, + name: "init$guard", + typ: types.NewPointer(tBool), } + p.Members[initguard.Name()] = initguard if prog.mode&GlobalDebug != 0 { p.SetDebugMode(true) @@ -260,10 +265,10 @@ func (prog *Program) AllPackages() []*Package { // // TODO(adonovan): rethink this function and the "importable" concept; // most packages are importable. This function assumes that all -// types.Package.Path values are unique within the ssa.Program, which is +// types.Package.Path values are unique within the ir.Program, which is // false---yet this function remains very convenient. // Clients should use (*Program).Package instead where possible. -// SSA doesn't really need a string-keyed map of packages. +// IR doesn't really need a string-keyed map of packages. // func (prog *Program) ImportedPackage(path string) *Package { return prog.imported[path] diff --git a/vendor/honnef.co/go/tools/ssa/doc.go b/vendor/honnef.co/go/tools/ir/doc.go similarity index 74% rename from vendor/honnef.co/go/tools/ssa/doc.go rename to vendor/honnef.co/go/tools/ir/doc.go index 0f71fda001..a5f42e4f47 100644 --- a/vendor/honnef.co/go/tools/ssa/doc.go +++ b/vendor/honnef.co/go/tools/ir/doc.go @@ -2,36 +2,34 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package ssa defines a representation of the elements of Go programs +// Package ir defines a representation of the elements of Go programs // (packages, types, functions, variables and constants) using a -// static single-assignment (SSA) form intermediate representation +// static single-information (SSI) form intermediate representation // (IR) for the bodies of functions. // // THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE. // -// For an introduction to SSA form, see +// For an introduction to SSA form, upon which SSI builds, see // http://en.wikipedia.org/wiki/Static_single_assignment_form. // This page provides a broader reading list: // http://www.dcs.gla.ac.uk/~jsinger/ssa.html. // -// The level of abstraction of the SSA form is intentionally close to +// For an introduction to SSI form, see The static single information +// form by C. Scott Ananian. +// +// The level of abstraction of the IR form is intentionally close to // the source language to facilitate construction of source analysis // tools. It is not intended for machine code generation. // -// All looping, branching and switching constructs are replaced with -// unstructured control flow. Higher-level control flow constructs -// such as multi-way branch can be reconstructed as needed; see -// ssautil.Switches() for an example. -// -// The simplest way to create the SSA representation of a package is +// The simplest way to create the IR of a package is // to load typed syntax trees using golang.org/x/tools/go/packages, then -// invoke the ssautil.Packages helper function. See ExampleLoadPackages +// invoke the irutil.Packages helper function. See ExampleLoadPackages // and ExampleWholeProgram for examples. -// The resulting ssa.Program contains all the packages and their -// members, but SSA code is not created for function bodies until a +// The resulting ir.Program contains all the packages and their +// members, but IR code is not created for function bodies until a // subsequent call to (*Package).Build or (*Program).Build. // -// The builder initially builds a naive SSA form in which all local +// The builder initially builds a naive IR form in which all local // variables are addresses of stack locations with explicit loads and // stores. Registerisation of eligible locals and φ-node insertion // using dominance and dataflow are then performed as a second pass @@ -44,7 +42,7 @@ // - Member: a named member of a Go package. // - Value: an expression that yields a value. // - Instruction: a statement that consumes values and performs computation. -// - Node: a Value or Instruction (emphasizing its membership in the SSA value graph) +// - Node: a Value or Instruction (emphasizing its membership in the IR value graph) // // A computation that yields a result implements both the Value and // Instruction interfaces. The following table shows for each @@ -53,47 +51,53 @@ // Value? Instruction? Member? // *Alloc ✔ ✔ // *BinOp ✔ ✔ +// *BlankStore ✔ // *Builtin ✔ // *Call ✔ ✔ // *ChangeInterface ✔ ✔ // *ChangeType ✔ ✔ -// *Const ✔ +// *Const ✔ ✔ // *Convert ✔ ✔ // *DebugRef ✔ -// *Defer ✔ +// *Defer ✔ ✔ // *Extract ✔ ✔ // *Field ✔ ✔ // *FieldAddr ✔ ✔ // *FreeVar ✔ // *Function ✔ ✔ (func) // *Global ✔ ✔ (var) -// *Go ✔ +// *Go ✔ ✔ // *If ✔ // *Index ✔ ✔ // *IndexAddr ✔ ✔ // *Jump ✔ -// *Lookup ✔ ✔ +// *Load ✔ ✔ // *MakeChan ✔ ✔ // *MakeClosure ✔ ✔ // *MakeInterface ✔ ✔ // *MakeMap ✔ ✔ // *MakeSlice ✔ ✔ -// *MapUpdate ✔ +// *MapLookup ✔ ✔ +// *MapUpdate ✔ ✔ // *NamedConst ✔ (const) // *Next ✔ ✔ // *Panic ✔ -// *Parameter ✔ +// *Parameter ✔ ✔ // *Phi ✔ ✔ // *Range ✔ ✔ +// *Recv ✔ ✔ // *Return ✔ // *RunDefers ✔ // *Select ✔ ✔ -// *Send ✔ +// *Send ✔ ✔ +// *Sigma ✔ ✔ // *Slice ✔ ✔ -// *Store ✔ +// *Store ✔ ✔ +// *StringLookup ✔ ✔ // *Type ✔ (type) // *TypeAssert ✔ ✔ // *UnOp ✔ ✔ +// *Unreachable ✔ // // Other key types in this package include: Program, Package, Function // and BasicBlock. @@ -102,7 +106,7 @@ // resolved internally, i.e. it does not rely on the names of Values, // Packages, Functions, Types or BasicBlocks for the correct // interpretation of the program. Only the identities of objects and -// the topology of the SSA and type graphs are semantically +// the topology of the IR and type graphs are semantically // significant. (There is one exception: Ids, used to identify field // and method names, contain strings.) Avoidance of name-based // operations simplifies the implementation of subsequent passes and @@ -111,7 +115,7 @@ // either accurate or unambiguous. The public API exposes a number of // name-based maps for client convenience. // -// The ssa/ssautil package provides various utilities that depend only +// The ir/irutil package provides various utilities that depend only // on the public API of this package. // // TODO(adonovan): Consider the exceptional control-flow implications @@ -120,6 +124,6 @@ // TODO(adonovan): write a how-to document for all the various cases // of trying to determine corresponding elements across the four // domains of source locations, ast.Nodes, types.Objects, -// ssa.Values/Instructions. +// ir.Values/Instructions. // -package ssa // import "honnef.co/go/tools/ssa" +package ir // import "honnef.co/go/tools/ir" diff --git a/vendor/honnef.co/go/tools/ssa/dom.go b/vendor/honnef.co/go/tools/ir/dom.go similarity index 51% rename from vendor/honnef.co/go/tools/ssa/dom.go rename to vendor/honnef.co/go/tools/ir/dom.go index a036be87c4..08c147df9b 100644 --- a/vendor/honnef.co/go/tools/ssa/dom.go +++ b/vendor/honnef.co/go/tools/ir/dom.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines algorithms related to dominance. @@ -20,6 +20,7 @@ package ssa import ( "bytes" "fmt" + "io" "math/big" "os" "sort" @@ -27,8 +28,7 @@ import ( // Idom returns the block that immediately dominates b: // its parent in the dominator tree, if any. -// Neither the entry node (b.Index==0) nor recover node -// (b==b.Parent().Recover()) have a parent. +// The entry node (b.Index==0) does not have a parent. // func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom } @@ -66,144 +66,211 @@ type domInfo struct { pre, post int32 // pre- and post-order numbering within domtree } -// ltState holds the working state for Lengauer-Tarjan algorithm -// (during which domInfo.pre is repurposed for CFG DFS preorder number). -type ltState struct { - // Each slice is indexed by b.Index. - sdom []*BasicBlock // b's semidominator - parent []*BasicBlock // b's parent in DFS traversal of CFG - ancestor []*BasicBlock // b's ancestor with least sdom -} - -// dfs implements the depth-first search part of the LT algorithm. -func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 { - preorder[i] = v - v.dom.pre = i // For now: DFS preorder of spanning tree of CFG - i++ - lt.sdom[v.Index] = v - lt.link(nil, v) - for _, w := range v.Succs { - if lt.sdom[w.Index] == nil { - lt.parent[w.Index] = v - i = lt.dfs(w, i, preorder) - } - } - return i -} - -// eval implements the EVAL part of the LT algorithm. -func (lt *ltState) eval(v *BasicBlock) *BasicBlock { - // TODO(adonovan): opt: do path compression per simple LT. - u := v - for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] { - if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre { - u = v - } - } - return u -} - -// link implements the LINK part of the LT algorithm. -func (lt *ltState) link(v, w *BasicBlock) { - lt.ancestor[w.Index] = v -} - // buildDomTree computes the dominator tree of f using the LT algorithm. // Precondition: all blocks are reachable (e.g. optimizeBlocks has been run). // -func buildDomTree(f *Function) { +func buildDomTree(fn *Function) { // The step numbers refer to the original LT paper; the // reordering is due to Georgiadis. // Clear any previous domInfo. - for _, b := range f.Blocks { + for _, b := range fn.Blocks { b.dom = domInfo{} } - n := len(f.Blocks) - // Allocate space for 5 contiguous [n]*BasicBlock arrays: - // sdom, parent, ancestor, preorder, buckets. - space := make([]*BasicBlock, 5*n) - lt := ltState{ - sdom: space[0:n], - parent: space[n : 2*n], - ancestor: space[2*n : 3*n], + idoms := make([]*BasicBlock, len(fn.Blocks)) + + order := make([]*BasicBlock, 0, len(fn.Blocks)) + seen := fn.blockset(0) + var dfs func(b *BasicBlock) + dfs = func(b *BasicBlock) { + if !seen.Add(b) { + return + } + for _, succ := range b.Succs { + dfs(succ) + } + if fn.fakeExits.Has(b) { + dfs(fn.Exit) + } + order = append(order, b) + b.post = len(order) - 1 + } + dfs(fn.Blocks[0]) + + for i := 0; i < len(order)/2; i++ { + o := len(order) - i - 1 + order[i], order[o] = order[o], order[i] } - // Step 1. Number vertices by depth-first preorder. - preorder := space[3*n : 4*n] - root := f.Blocks[0] - prenum := lt.dfs(root, 0, preorder) - recover := f.Recover - if recover != nil { - lt.dfs(recover, prenum, preorder) - } + idoms[fn.Blocks[0].Index] = fn.Blocks[0] + changed := true + for changed { + changed = false + // iterate over all nodes in reverse postorder, except for the + // entry node + for _, b := range order[1:] { + var newIdom *BasicBlock + do := func(p *BasicBlock) { + if idoms[p.Index] == nil { + return + } + if newIdom == nil { + newIdom = p + } else { + finger1 := p + finger2 := newIdom + for finger1 != finger2 { + for finger1.post < finger2.post { + finger1 = idoms[finger1.Index] + } + for finger2.post < finger1.post { + finger2 = idoms[finger2.Index] + } + } + newIdom = finger1 + } + } + for _, p := range b.Preds { + do(p) + } + if b == fn.Exit { + for _, p := range fn.Blocks { + if fn.fakeExits.Has(p) { + do(p) + } + } + } - buckets := space[4*n : 5*n] - copy(buckets, preorder) - - // In reverse preorder... - for i := int32(n) - 1; i > 0; i-- { - w := preorder[i] - - // Step 3. Implicitly define the immediate dominator of each node. - for v := buckets[i]; v != w; v = buckets[v.dom.pre] { - u := lt.eval(v) - if lt.sdom[u.Index].dom.pre < i { - v.dom.idom = u - } else { - v.dom.idom = w + if idoms[b.Index] != newIdom { + idoms[b.Index] = newIdom + changed = true } } + } - // Step 2. Compute the semidominators of all nodes. - lt.sdom[w.Index] = lt.parent[w.Index] - for _, v := range w.Preds { - u := lt.eval(v) - if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre { - lt.sdom[w.Index] = lt.sdom[u.Index] - } + for i, b := range idoms { + fn.Blocks[i].dom.idom = b + if b == nil { + // malformed CFG + continue } - - lt.link(lt.parent[w.Index], w) - - if lt.parent[w.Index] == lt.sdom[w.Index] { - w.dom.idom = lt.parent[w.Index] - } else { - buckets[i] = buckets[lt.sdom[w.Index].dom.pre] - buckets[lt.sdom[w.Index].dom.pre] = w + if i == b.Index { + continue } + b.dom.children = append(b.dom.children, fn.Blocks[i]) } - // The final 'Step 3' is now outside the loop. - for v := buckets[0]; v != root; v = buckets[v.dom.pre] { - v.dom.idom = root - } + numberDomTree(fn.Blocks[0], 0, 0) - // Step 4. Explicitly define the immediate dominator of each - // node, in preorder. - for _, w := range preorder[1:] { - if w == root || w == recover { - w.dom.idom = nil - } else { - if w.dom.idom != lt.sdom[w.Index] { - w.dom.idom = w.dom.idom.dom.idom - } - // Calculate Children relation as inverse of Idom. - w.dom.idom.dom.children = append(w.dom.idom.dom.children, w) - } - } - - pre, post := numberDomTree(root, 0, 0) - if recover != nil { - numberDomTree(recover, pre, post) - } - - // printDomTreeDot(os.Stderr, f) // debugging + // printDomTreeDot(os.Stderr, fn) // debugging // printDomTreeText(os.Stderr, root, 0) // debugging - if f.Prog.mode&SanityCheckFunctions != 0 { - sanityCheckDomTree(f) + if fn.Prog.mode&SanityCheckFunctions != 0 { + sanityCheckDomTree(fn) + } +} + +// buildPostDomTree is like buildDomTree, but builds the post-dominator tree instead. +func buildPostDomTree(fn *Function) { + // The step numbers refer to the original LT paper; the + // reordering is due to Georgiadis. + + // Clear any previous domInfo. + for _, b := range fn.Blocks { + b.pdom = domInfo{} + } + + idoms := make([]*BasicBlock, len(fn.Blocks)) + + order := make([]*BasicBlock, 0, len(fn.Blocks)) + seen := fn.blockset(0) + var dfs func(b *BasicBlock) + dfs = func(b *BasicBlock) { + if !seen.Add(b) { + return + } + for _, pred := range b.Preds { + dfs(pred) + } + if b == fn.Exit { + for _, p := range fn.Blocks { + if fn.fakeExits.Has(p) { + dfs(p) + } + } + } + order = append(order, b) + b.post = len(order) - 1 + } + dfs(fn.Exit) + + for i := 0; i < len(order)/2; i++ { + o := len(order) - i - 1 + order[i], order[o] = order[o], order[i] + } + + idoms[fn.Exit.Index] = fn.Exit + changed := true + for changed { + changed = false + // iterate over all nodes in reverse postorder, except for the + // exit node + for _, b := range order[1:] { + var newIdom *BasicBlock + do := func(p *BasicBlock) { + if idoms[p.Index] == nil { + return + } + if newIdom == nil { + newIdom = p + } else { + finger1 := p + finger2 := newIdom + for finger1 != finger2 { + for finger1.post < finger2.post { + finger1 = idoms[finger1.Index] + } + for finger2.post < finger1.post { + finger2 = idoms[finger2.Index] + } + } + newIdom = finger1 + } + } + for _, p := range b.Succs { + do(p) + } + if fn.fakeExits.Has(b) { + do(fn.Exit) + } + + if idoms[b.Index] != newIdom { + idoms[b.Index] = newIdom + changed = true + } + } + } + + for i, b := range idoms { + fn.Blocks[i].pdom.idom = b + if b == nil { + // malformed CFG + continue + } + if i == b.Index { + continue + } + b.pdom.children = append(b.pdom.children, fn.Blocks[i]) + } + + numberPostDomTree(fn.Exit, 0, 0) + + // printPostDomTreeDot(os.Stderr, fn) // debugging + // printPostDomTreeText(os.Stderr, fn.Exit, 0) // debugging + + if fn.Prog.mode&SanityCheckFunctions != 0 { // XXX + sanityCheckDomTree(fn) // XXX } } @@ -222,6 +289,21 @@ func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) { return pre, post } +// numberPostDomTree sets the pre- and post-order numbers of a depth-first +// traversal of the post-dominator tree rooted at v. These are used to +// answer post-dominance queries in constant time. +// +func numberPostDomTree(v *BasicBlock, pre, post int32) (int32, int32) { + v.pdom.pre = pre + pre++ + for _, child := range v.pdom.children { + pre, post = numberPostDomTree(child, pre, post) + } + v.pdom.post = post + post++ + return pre, post +} + // Testing utilities ---------------------------------------- // sanityCheckDomTree checks the correctness of the dominator tree @@ -243,8 +325,8 @@ func sanityCheckDomTree(f *Function) { all.Set(one).Lsh(&all, uint(n)).Sub(&all, one) // Initialization. - for i, b := range f.Blocks { - if i == 0 || b == f.Recover { + for i := range f.Blocks { + if i == 0 { // A root is dominated only by itself. D[i].SetBit(&D[0], 0, 1) } else { @@ -258,7 +340,7 @@ func sanityCheckDomTree(f *Function) { for changed := true; changed; { changed = false for i, b := range f.Blocks { - if i == 0 || b == f.Recover { + if i == 0 { continue } // Compute intersection across predecessors. @@ -267,6 +349,13 @@ func sanityCheckDomTree(f *Function) { for _, pred := range b.Preds { x.And(&x, &D[pred.Index]) } + if b == f.Exit { + for _, p := range f.Blocks { + if f.fakeExits.Has(p) { + x.And(&x, &D[p.Index]) + } + } + } x.SetBit(&x, i, 1) // a block always dominates itself. if D[i].Cmp(&x) != 0 { D[i].Set(&x) @@ -276,14 +365,10 @@ func sanityCheckDomTree(f *Function) { } // Check the entire relation. O(n^2). - // The Recover block (if any) must be treated specially so we skip it. ok := true for i := 0; i < n; i++ { for j := 0; j < n; j++ { b, c := f.Blocks[i], f.Blocks[j] - if c == f.Recover { - continue - } actual := b.Dominates(c) expected := D[j].Bit(i) == 1 if actual != expected { @@ -321,7 +406,7 @@ func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) { // printDomTreeDot prints the dominator tree of f in AT&T GraphViz // (.dot) format. //lint:ignore U1000 used during debugging -func printDomTreeDot(buf *bytes.Buffer, f *Function) { +func printDomTreeDot(buf io.Writer, f *Function) { fmt.Fprintln(buf, "//", f) fmt.Fprintln(buf, "digraph domtree {") for i, b := range f.Blocks { @@ -341,3 +426,36 @@ func printDomTreeDot(buf *bytes.Buffer, f *Function) { } fmt.Fprintln(buf, "}") } + +// printDomTree prints the dominator tree as text, using indentation. +//lint:ignore U1000 used during debugging +func printPostDomTreeText(buf io.Writer, v *BasicBlock, indent int) { + fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v) + for _, child := range v.pdom.children { + printPostDomTreeText(buf, child, indent+1) + } +} + +// printDomTreeDot prints the dominator tree of f in AT&T GraphViz +// (.dot) format. +//lint:ignore U1000 used during debugging +func printPostDomTreeDot(buf io.Writer, f *Function) { + fmt.Fprintln(buf, "//", f) + fmt.Fprintln(buf, "digraph pdomtree {") + for _, b := range f.Blocks { + v := b.pdom + fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post) + // TODO(adonovan): improve appearance of edges + // belonging to both dominator tree and CFG. + + // Dominator tree edge. + if b != f.Exit { + fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.pdom.pre, v.pre) + } + // CFG edges. + for _, pred := range b.Preds { + fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.pdom.pre, v.pre) + } + } + fmt.Fprintln(buf, "}") +} diff --git a/vendor/honnef.co/go/tools/ssa/emit.go b/vendor/honnef.co/go/tools/ir/emit.go similarity index 75% rename from vendor/honnef.co/go/tools/ssa/emit.go rename to vendor/honnef.co/go/tools/ir/emit.go index 6bf9ec32da..5fa137af9e 100644 --- a/vendor/honnef.co/go/tools/ssa/emit.go +++ b/vendor/honnef.co/go/tools/ir/emit.go @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir -// Helpers for emitting SSA instructions. +// Helpers for emitting IR instructions. import ( "fmt" "go/ast" + "go/constant" "go/token" "go/types" ) @@ -16,24 +17,32 @@ import ( // emitNew emits to f a new (heap Alloc) instruction allocating an // object of type typ. pos is the optional source location. // -func emitNew(f *Function, typ types.Type, pos token.Pos) *Alloc { +func emitNew(f *Function, typ types.Type, source ast.Node) *Alloc { v := &Alloc{Heap: true} v.setType(types.NewPointer(typ)) - v.setPos(pos) - f.emit(v) + f.emit(v, source) return v } // emitLoad emits to f an instruction to load the address addr into a // new temporary, and returns the value so defined. // -func emitLoad(f *Function, addr Value) *UnOp { - v := &UnOp{Op: token.MUL, X: addr} +func emitLoad(f *Function, addr Value, source ast.Node) *Load { + v := &Load{X: addr} v.setType(deref(addr.Type())) - f.emit(v) + f.emit(v, source) return v } +func emitRecv(f *Function, ch Value, commaOk bool, typ types.Type, source ast.Node) Value { + recv := &Recv{ + Chan: ch, + CommaOk: commaOk, + } + recv.setType(typ) + return f.emit(recv, source) +} + // emitDebugRef emits to f a DebugRef pseudo-instruction associating // expression e with value v. // @@ -61,7 +70,7 @@ func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) { Expr: e, IsAddr: isAddr, object: obj, - }) + }, nil) } // emitArith emits to f code to compute the binary operation op(x, y) @@ -69,19 +78,19 @@ func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) { // (Use emitCompare() for comparisons and Builder.logicalBinop() for // non-eager operations.) // -func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.Pos) Value { +func emitArith(f *Function, op token.Token, x, y Value, t types.Type, source ast.Node) Value { switch op { case token.SHL, token.SHR: - x = emitConv(f, x, t) + x = emitConv(f, x, t, source) // y may be signed or an 'untyped' constant. // TODO(adonovan): whence signed values? if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 { - y = emitConv(f, y, types.Typ[types.Uint64]) + y = emitConv(f, y, types.Typ[types.Uint64], source) } case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: - x = emitConv(f, x, t) - y = emitConv(f, y, t) + x = emitConv(f, x, t, source) + y = emitConv(f, y, t, source) default: panic("illegal op in emitArith: " + op.String()) @@ -92,15 +101,14 @@ func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token. X: x, Y: y, } - v.setPos(pos) v.setType(t) - return f.emit(v) + return f.emit(v, source) } // emitCompare emits to f code compute the boolean result of // comparison comparison 'x op y'. // -func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { +func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) Value { xt := x.Type().Underlying() yt := y.Type().Underlying() @@ -111,7 +119,7 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { // if e==true { ... } // even in the case when e's type is an interface. // TODO(adonovan): opt: generalise to x==true, false!=y, etc. - if x == vTrue && op == token.EQL { + if x, ok := x.(*Const); ok && op == token.EQL && x.Value != nil && x.Value.Kind() == constant.Bool && constant.BoolVal(x.Value) { if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 { return y } @@ -120,13 +128,13 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { if types.Identical(xt, yt) { // no conversion necessary } else if _, ok := xt.(*types.Interface); ok { - y = emitConv(f, y, x.Type()) + y = emitConv(f, y, x.Type(), source) } else if _, ok := yt.(*types.Interface); ok { - x = emitConv(f, x, y.Type()) + x = emitConv(f, x, y.Type(), source) } else if _, ok := x.(*Const); ok { - x = emitConv(f, x, y.Type()) + x = emitConv(f, x, y.Type(), source) } else if _, ok := y.(*Const); ok { - y = emitConv(f, y, x.Type()) + y = emitConv(f, y, x.Type(), source) //lint:ignore SA9003 no-op } else { // other cases, e.g. channels. No-op. @@ -137,9 +145,8 @@ func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { X: x, Y: y, } - v.setPos(pos) v.setType(tBool) - return f.emit(v) + return f.emit(v, source) } // isValuePreserving returns true if a conversion from ut_src to @@ -171,7 +178,7 @@ func isValuePreserving(ut_src, ut_dst types.Type) bool { // by language assignability rules in assignments, parameter passing, // etc. Conversions cannot fail dynamically. // -func emitConv(f *Function, val Value, typ types.Type) Value { +func emitConv(f *Function, val Value, typ types.Type, source ast.Node) Value { t_src := val.Type() // Identical types? Conversion is a no-op. @@ -186,7 +193,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value { if isValuePreserving(ut_src, ut_dst) { c := &ChangeType{X: val} c.setType(typ) - return f.emit(c) + return f.emit(c, source) } // Conversion to, or construction of a value of, an interface type? @@ -195,23 +202,23 @@ func emitConv(f *Function, val Value, typ types.Type) Value { if _, ok := ut_src.(*types.Interface); ok { c := &ChangeInterface{X: val} c.setType(typ) - return f.emit(c) + return f.emit(c, source) } // Untyped nil constant? Return interface-typed nil constant. if ut_src == tUntypedNil { - return nilConst(typ) + return emitConst(f, nilConst(typ)) } // Convert (non-nil) "untyped" literals to their default type. if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 { - val = emitConv(f, val, DefaultType(ut_src)) + val = emitConv(f, val, types.Default(ut_src), source) } f.Pkg.Prog.needMethodsOf(val.Type()) mi := &MakeInterface{X: val} mi.setType(typ) - return f.emit(mi) + return f.emit(mi, source) } // Conversion of a compile-time constant value? @@ -222,7 +229,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value { // constant of the destination type and // (initially) the same abstract value. // We don't truncate the value yet. - return NewConst(c.Value, typ) + return emitConst(f, NewConst(c.Value, typ)) } // We're converting from constant to non-constant type, @@ -237,7 +244,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value { if ok1 || ok2 { c := &Convert{X: val} c.setType(typ) - return f.emit(c) + return f.emit(c, source) } panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ)) @@ -246,72 +253,75 @@ func emitConv(f *Function, val Value, typ types.Type) Value { // emitStore emits to f an instruction to store value val at location // addr, applying implicit conversions as required by assignability rules. // -func emitStore(f *Function, addr, val Value, pos token.Pos) *Store { +func emitStore(f *Function, addr, val Value, source ast.Node) *Store { s := &Store{ Addr: addr, - Val: emitConv(f, val, deref(addr.Type())), - pos: pos, + Val: emitConv(f, val, deref(addr.Type()), source), } - f.emit(s) + // make sure we call getMem after the call to emitConv, which may + // itself update the memory state + f.emit(s, source) return s } // emitJump emits to f a jump to target, and updates the control-flow graph. // Postcondition: f.currentBlock is nil. // -func emitJump(f *Function, target *BasicBlock) { +func emitJump(f *Function, target *BasicBlock, source ast.Node) *Jump { b := f.currentBlock - b.emit(new(Jump)) + j := new(Jump) + b.emit(j, source) addEdge(b, target) f.currentBlock = nil + return j } // emitIf emits to f a conditional jump to tblock or fblock based on // cond, and updates the control-flow graph. // Postcondition: f.currentBlock is nil. // -func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) { +func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock, source ast.Node) *If { b := f.currentBlock - b.emit(&If{Cond: cond}) + stmt := &If{Cond: cond} + b.emit(stmt, source) addEdge(b, tblock) addEdge(b, fblock) f.currentBlock = nil + return stmt } // emitExtract emits to f an instruction to extract the index'th // component of tuple. It returns the extracted value. // -func emitExtract(f *Function, tuple Value, index int) Value { +func emitExtract(f *Function, tuple Value, index int, source ast.Node) Value { e := &Extract{Tuple: tuple, Index: index} e.setType(tuple.Type().(*types.Tuple).At(index).Type()) - return f.emit(e) + return f.emit(e, source) } // emitTypeAssert emits to f a type assertion value := x.(t) and // returns the value. x.Type() must be an interface. // -func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value { +func emitTypeAssert(f *Function, x Value, t types.Type, source ast.Node) Value { a := &TypeAssert{X: x, AssertedType: t} - a.setPos(pos) a.setType(t) - return f.emit(a) + return f.emit(a, source) } // emitTypeTest emits to f a type test value,ok := x.(t) and returns // a (value, ok) tuple. x.Type() must be an interface. // -func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value { +func emitTypeTest(f *Function, x Value, t types.Type, source ast.Node) Value { a := &TypeAssert{ X: x, AssertedType: t, CommaOk: true, } - a.setPos(pos) a.setType(types.NewTuple( newVar("value", t), varOk, )) - return f.emit(a) + return f.emit(a, source) } // emitTailCall emits to f a function call in tail position. The @@ -320,7 +330,7 @@ func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value { // Precondition: f does/will not use deferred procedure calls. // Postcondition: f.currentBlock is nil. // -func emitTailCall(f *Function, call *Call) { +func emitTailCall(f *Function, call *Call, source ast.Node) { tresults := f.Signature.Results() nr := tresults.Len() if nr == 1 { @@ -328,7 +338,7 @@ func emitTailCall(f *Function, call *Call) { } else { call.typ = tresults } - tuple := f.emit(call) + tuple := f.emit(call, source) var ret Return switch nr { case 0: @@ -337,7 +347,7 @@ func emitTailCall(f *Function, call *Call) { ret.Results = []Value{tuple} default: for i := 0; i < nr; i++ { - v := emitExtract(f, tuple, i) + v := emitExtract(f, tuple, i, source) // TODO(adonovan): in principle, this is required: // v = emitConv(f, o.Type, f.Signature.Results[i].Type) // but in practice emitTailCall is only used when @@ -345,7 +355,11 @@ func emitTailCall(f *Function, call *Call) { ret.Results = append(ret.Results, v) } } - f.emit(&ret) + + f.Exit = f.newBasicBlock("exit") + emitJump(f, f.Exit, source) + f.currentBlock = f.Exit + f.emit(&ret, source) f.currentBlock = nil } @@ -357,7 +371,7 @@ func emitTailCall(f *Function, call *Call) { // a field; if it is the value of a struct, the result will be the // value of a field. // -func emitImplicitSelections(f *Function, v Value, indices []int) Value { +func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node) Value { for _, index := range indices { fld := deref(v.Type()).Underlying().(*types.Struct).Field(index) @@ -367,10 +381,10 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value { Field: index, } instr.setType(types.NewPointer(fld.Type())) - v = f.emit(instr) + v = f.emit(instr, source) // Load the field's value iff indirectly embedded. if isPointer(fld.Type()) { - v = emitLoad(f, v) + v = emitLoad(f, v, source) } } else { instr := &Field{ @@ -378,7 +392,7 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value { Field: index, } instr.setType(fld.Type()) - v = f.emit(instr) + v = f.emit(instr, source) } } return v @@ -398,21 +412,21 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast. X: v, Field: index, } - instr.setPos(id.Pos()) + instr.setSource(id) instr.setType(types.NewPointer(fld.Type())) - v = f.emit(instr) + v = f.emit(instr, id) // Load the field's value iff we don't want its address. if !wantAddr { - v = emitLoad(f, v) + v = emitLoad(f, v, id) } } else { instr := &Field{ X: v, Field: index, } - instr.setPos(id.Pos()) + instr.setSource(id) instr.setType(fld.Type()) - v = f.emit(instr) + v = f.emit(instr, id) } emitDebugRef(f, id, v, wantAddr) return v @@ -421,49 +435,16 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast. // zeroValue emits to f code to produce a zero value of type t, // and returns it. // -func zeroValue(f *Function, t types.Type) Value { +func zeroValue(f *Function, t types.Type, source ast.Node) Value { switch t.Underlying().(type) { case *types.Struct, *types.Array: - return emitLoad(f, f.addLocal(t, token.NoPos)) + return emitLoad(f, f.addLocal(t, source), source) default: - return zeroConst(t) + return emitConst(f, zeroConst(t)) } } -// createRecoverBlock emits to f a block of code to return after a -// recovered panic, and sets f.Recover to it. -// -// If f's result parameters are named, the code loads and returns -// their current values, otherwise it returns the zero values of their -// type. -// -// Idempotent. -// -func createRecoverBlock(f *Function) { - if f.Recover != nil { - return // already created - } - saved := f.currentBlock - - f.Recover = f.newBasicBlock("recover") - f.currentBlock = f.Recover - - var results []Value - if f.namedResults != nil { - // Reload NRPs to form value tuple. - for _, r := range f.namedResults { - results = append(results, emitLoad(f, r)) - } - } else { - R := f.Signature.Results() - for i, n := 0, R.Len(); i < n; i++ { - T := R.At(i).Type() - - // Return zero value of each result type. - results = append(results, zeroValue(f, T)) - } - } - f.emit(&Return{Results: results}) - - f.currentBlock = saved +func emitConst(f *Function, c *Const) *Const { + f.consts = append(f.consts, c) + return c } diff --git a/vendor/honnef.co/go/tools/ir/exits.go b/vendor/honnef.co/go/tools/ir/exits.go new file mode 100644 index 0000000000..10cda7bb66 --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/exits.go @@ -0,0 +1,271 @@ +package ir + +import ( + "go/types" +) + +func (b *builder) buildExits(fn *Function) { + if obj := fn.Object(); obj != nil { + switch obj.Pkg().Path() { + case "runtime": + switch obj.Name() { + case "exit": + fn.WillExit = true + return + case "throw": + fn.WillExit = true + return + case "Goexit": + fn.WillUnwind = true + return + } + case "github.com/sirupsen/logrus": + switch obj.(*types.Func).FullName() { + case "(*github.com/sirupsen/logrus.Logger).Exit": + // Technically, this method does not unconditionally exit + // the process. It dynamically calls a function stored in + // the logger. If the function is nil, it defaults to + // os.Exit. + // + // The main intent of this method is to terminate the + // process, and that's what the vast majority of people + // will use it for. We'll happily accept some false + // negatives to avoid a lot of false positives. + fn.WillExit = true + return + case "(*github.com/sirupsen/logrus.Logger).Panic", + "(*github.com/sirupsen/logrus.Logger).Panicf", + "(*github.com/sirupsen/logrus.Logger).Panicln": + + // These methods will always panic, but that's not + // statically known from the code alone, because they + // take a detour through the generic Log methods. + fn.WillUnwind = true + return + case "(*github.com/sirupsen/logrus.Entry).Panicf", + "(*github.com/sirupsen/logrus.Entry).Panicln": + + // Entry.Panic has an explicit panic, but Panicf and + // Panicln do not, relying fully on the generic Log + // method. + fn.WillUnwind = true + return + case "(*github.com/sirupsen/logrus.Logger).Log", + "(*github.com/sirupsen/logrus.Logger).Logf", + "(*github.com/sirupsen/logrus.Logger).Logln": + // TODO(dh): we cannot handle these case. Whether they + // exit or unwind depends on the level, which is set + // via the first argument. We don't currently support + // call-site-specific exit information. + } + } + } + + buildDomTree(fn) + + isRecoverCall := func(instr Instruction) bool { + if instr, ok := instr.(*Call); ok { + if builtin, ok := instr.Call.Value.(*Builtin); ok { + if builtin.Name() == "recover" { + return true + } + } + } + return false + } + + // All panics branch to the exit block, which means that if every + // possible path through the function panics, then all + // predecessors of the exit block must panic. + willPanic := true + for _, pred := range fn.Exit.Preds { + if _, ok := pred.Control().(*Panic); !ok { + willPanic = false + } + } + if willPanic { + recovers := false + recoverLoop: + for _, u := range fn.Blocks { + for _, instr := range u.Instrs { + if instr, ok := instr.(*Defer); ok { + call := instr.Call.StaticCallee() + if call == nil { + // not a static call, so we can't be sure the + // deferred call isn't calling recover + recovers = true + break recoverLoop + } + if len(call.Blocks) == 0 { + // external function, we don't know what's + // happening inside it + // + // TODO(dh): this includes functions from + // imported packages, due to how go/analysis + // works. We could introduce another fact, + // like we've done for exiting and unwinding, + // but it doesn't seem worth it. Virtually all + // uses of recover will be in closures. + recovers = true + break recoverLoop + } + for _, y := range call.Blocks { + for _, instr2 := range y.Instrs { + if isRecoverCall(instr2) { + recovers = true + break recoverLoop + } + } + } + } + } + } + if !recovers { + fn.WillUnwind = true + return + } + } + + // TODO(dh): don't check that any specific call dominates the exit + // block. instead, check that all calls combined cover every + // possible path through the function. + exits := NewBlockSet(len(fn.Blocks)) + unwinds := NewBlockSet(len(fn.Blocks)) + for _, u := range fn.Blocks { + for _, instr := range u.Instrs { + if instr, ok := instr.(CallInstruction); ok { + switch instr.(type) { + case *Defer, *Call: + default: + continue + } + if instr.Common().IsInvoke() { + // give up + return + } + var call *Function + switch instr.Common().Value.(type) { + case *Function, *MakeClosure: + call = instr.Common().StaticCallee() + case *Builtin: + // the only builtins that affect control flow are + // panic and recover, and we've already handled + // those + continue + default: + // dynamic dispatch + return + } + // buildFunction is idempotent. if we're part of a + // (mutually) recursive call chain, then buildFunction + // will immediately return, and fn.WillExit will be false. + if call.Package() == fn.Package() { + b.buildFunction(call) + } + dom := u.Dominates(fn.Exit) + if call.WillExit { + if dom { + fn.WillExit = true + return + } + exits.Add(u) + } else if call.WillUnwind { + if dom { + fn.WillUnwind = true + return + } + unwinds.Add(u) + } + } + } + } + + // depth-first search trying to find a path to the exit block that + // doesn't cross any of the blacklisted blocks + seen := NewBlockSet(len(fn.Blocks)) + var findPath func(root *BasicBlock, bl *BlockSet) bool + findPath = func(root *BasicBlock, bl *BlockSet) bool { + if root == fn.Exit { + return true + } + if seen.Has(root) { + return false + } + if bl.Has(root) { + return false + } + seen.Add(root) + for _, succ := range root.Succs { + if findPath(succ, bl) { + return true + } + } + return false + } + + if exits.Num() > 0 { + if !findPath(fn.Blocks[0], exits) { + fn.WillExit = true + return + } + } + if unwinds.Num() > 0 { + seen.Clear() + if !findPath(fn.Blocks[0], unwinds) { + fn.WillUnwind = true + return + } + } +} + +func (b *builder) addUnreachables(fn *Function) { + for _, bb := range fn.Blocks { + for i, instr := range bb.Instrs { + if instr, ok := instr.(*Call); ok { + var call *Function + switch v := instr.Common().Value.(type) { + case *Function: + call = v + case *MakeClosure: + call = v.Fn.(*Function) + } + if call == nil { + continue + } + if call.Package() == fn.Package() { + // make sure we have information on all functions in this package + b.buildFunction(call) + } + if call.WillExit { + // This call will cause the process to terminate. + // Remove remaining instructions in the block and + // replace any control flow with Unreachable. + for _, succ := range bb.Succs { + succ.removePred(bb) + } + bb.Succs = bb.Succs[:0] + + bb.Instrs = bb.Instrs[:i+1] + bb.emit(new(Unreachable), instr.Source()) + addEdge(bb, fn.Exit) + break + } else if call.WillUnwind { + // This call will cause the goroutine to terminate + // and defers to run (i.e. a panic or + // runtime.Goexit). Remove remaining instructions + // in the block and replace any control flow with + // an unconditional jump to the exit block. + for _, succ := range bb.Succs { + succ.removePred(bb) + } + bb.Succs = bb.Succs[:0] + + bb.Instrs = bb.Instrs[:i+1] + bb.emit(new(Jump), instr.Source()) + addEdge(bb, fn.Exit) + break + } + } + } + } +} diff --git a/vendor/honnef.co/go/tools/ssa/func.go b/vendor/honnef.co/go/tools/ir/func.go similarity index 65% rename from vendor/honnef.co/go/tools/ssa/func.go rename to vendor/honnef.co/go/tools/ir/func.go index 222eea6418..386d82b670 100644 --- a/vendor/honnef.co/go/tools/ssa/func.go +++ b/vendor/honnef.co/go/tools/ir/func.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file implements the Function and BasicBlock types. @@ -10,6 +10,8 @@ import ( "bytes" "fmt" "go/ast" + "go/constant" + "go/format" "go/token" "go/types" "io" @@ -23,6 +25,29 @@ func addEdge(from, to *BasicBlock) { to.Preds = append(to.Preds, from) } +// Control returns the last instruction in the block. +func (b *BasicBlock) Control() Instruction { + if len(b.Instrs) == 0 { + return nil + } + return b.Instrs[len(b.Instrs)-1] +} + +// SIgmaFor returns the sigma node for v coming from pred. +func (b *BasicBlock) SigmaFor(v Value, pred *BasicBlock) *Sigma { + for _, instr := range b.Instrs { + sigma, ok := instr.(*Sigma) + if !ok { + // no more sigmas + return nil + } + if sigma.From == pred && sigma.X == v { + return sigma + } + } + return nil +} + // Parent returns the function that contains block b. func (b *BasicBlock) Parent() *Function { return b.parent } @@ -36,7 +61,8 @@ func (b *BasicBlock) String() string { // emit appends an instruction to the current basic block. // If the instruction defines a Value, it is returned. // -func (b *BasicBlock) emit(i Instruction) Value { +func (b *BasicBlock) emit(i Instruction, source ast.Node) Value { + i.setSource(source) i.setBlock(b) b.Instrs = append(b.Instrs, i) v, _ := i.(Value) @@ -54,6 +80,16 @@ func (b *BasicBlock) predIndex(c *BasicBlock) int { panic(fmt.Sprintf("no edge %s -> %s", c, b)) } +// succIndex returns the i such that b.Succs[i] == c or -1 if there is none. +func (b *BasicBlock) succIndex(c *BasicBlock) int { + for i, succ := range b.Succs { + if succ == c { + return i + } + } + return -1 +} + // hasPhi returns true if b.Instrs contains φ-nodes. func (b *BasicBlock) hasPhi() bool { _, ok := b.Instrs[0].(*Phi) @@ -96,10 +132,6 @@ func (b *BasicBlock) replaceSucc(p, q *BasicBlock) { } } -func (b *BasicBlock) RemovePred(p *BasicBlock) { - b.removePred(p) -} - // removePred removes all occurrences of p in b's // predecessor list and φ-nodes. // Ordinarily there should be at most one. @@ -173,23 +205,33 @@ func (f *Function) labelledBlock(label *ast.Ident) *lblock { // addParam adds a (non-escaping) parameter to f.Params of the // specified name, type and source position. // -func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter { - v := &Parameter{ - name: name, - typ: typ, - pos: pos, - parent: f, +func (f *Function) addParam(name string, typ types.Type, source ast.Node) *Parameter { + var b *BasicBlock + if len(f.Blocks) > 0 { + b = f.Blocks[0] } + v := &Parameter{ + name: name, + } + v.setBlock(b) + v.setType(typ) + v.setSource(source) f.Params = append(f.Params, v) + if b != nil { + // There may be no blocks if this function has no body. We + // still create params, but aren't interested in the + // instruction. + f.Blocks[0].Instrs = append(f.Blocks[0].Instrs, v) + } return v } -func (f *Function) addParamObj(obj types.Object) *Parameter { +func (f *Function) addParamObj(obj types.Object, source ast.Node) *Parameter { name := obj.Name() if name == "" { name = fmt.Sprintf("arg%d", len(f.Params)) } - param := f.addParam(name, obj.Type(), obj.Pos()) + param := f.addParam(name, obj.Type(), source) param.object = obj return param } @@ -198,25 +240,61 @@ func (f *Function) addParamObj(obj types.Object) *Parameter { // stack; the function body will load/store the spilled location. // Subsequent lifting will eliminate spills where possible. // -func (f *Function) addSpilledParam(obj types.Object) { - param := f.addParamObj(obj) - spill := &Alloc{Comment: obj.Name()} +func (f *Function) addSpilledParam(obj types.Object, source ast.Node) { + param := f.addParamObj(obj, source) + spill := &Alloc{} spill.setType(types.NewPointer(obj.Type())) - spill.setPos(obj.Pos()) + spill.source = source f.objects[obj] = spill f.Locals = append(f.Locals, spill) - f.emit(spill) - f.emit(&Store{Addr: spill, Val: param}) + f.emit(spill, source) + emitStore(f, spill, param, source) + // f.emit(&Store{Addr: spill, Val: param}) } -// startBody initializes the function prior to generating SSA code for its body. +// startBody initializes the function prior to generating IR code for its body. // Precondition: f.Type() already set. // func (f *Function) startBody() { - f.currentBlock = f.newBasicBlock("entry") + entry := f.newBasicBlock("entry") + f.currentBlock = entry f.objects = make(map[types.Object]Value) // needed for some synthetics, e.g. init } +func (f *Function) blockset(i int) *BlockSet { + bs := &f.blocksets[i] + if len(bs.values) != len(f.Blocks) { + if cap(bs.values) >= len(f.Blocks) { + bs.values = bs.values[:len(f.Blocks)] + bs.Clear() + } else { + bs.values = make([]bool, len(f.Blocks)) + } + } else { + bs.Clear() + } + return bs +} + +func (f *Function) exitBlock() { + old := f.currentBlock + + f.Exit = f.newBasicBlock("exit") + f.currentBlock = f.Exit + + ret := f.results() + results := make([]Value, len(ret)) + // Run function calls deferred in this + // function when explicitly returning from it. + f.emit(new(RunDefers), nil) + for i, r := range ret { + results[i] = emitLoad(f, r, nil) + } + + f.emit(&Return{Results: results}, nil) + f.currentBlock = old +} + // createSyntacticParams populates f.Params and generates code (spills // and named result locals) for all the parameters declared in the // syntax. In addition it populates the f.objects mapping. @@ -231,11 +309,11 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func if recv != nil { for _, field := range recv.List { for _, n := range field.Names { - f.addSpilledParam(f.Pkg.info.Defs[n]) + f.addSpilledParam(f.Pkg.info.Defs[n], n) } // Anonymous receiver? No need to spill. if field.Names == nil { - f.addParamObj(f.Signature.Recv()) + f.addParamObj(f.Signature.Recv(), field) } } } @@ -245,11 +323,11 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func n := len(f.Params) // 1 if has recv, 0 otherwise for _, field := range functype.Params.List { for _, n := range field.Names { - f.addSpilledParam(f.Pkg.info.Defs[n]) + f.addSpilledParam(f.Pkg.info.Defs[n], n) } // Anonymous parameter? No need to spill. if field.Names == nil { - f.addParamObj(f.Signature.Params().At(len(f.Params) - n)) + f.addParamObj(f.Signature.Params().At(len(f.Params)-n), field) } } } @@ -262,24 +340,27 @@ func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.Func f.namedResults = append(f.namedResults, f.addLocalForIdent(n)) } } + + if len(f.namedResults) == 0 { + sig := f.Signature.Results() + for i := 0; i < sig.Len(); i++ { + // XXX position information + v := f.addLocal(sig.At(i).Type(), nil) + f.implicitResults = append(f.implicitResults, v) + } + } } } -// numberRegisters assigns numbers to all SSA registers -// (value-defining Instructions) in f, to aid debugging. -// (Non-Instruction Values are named at construction.) -// -func numberRegisters(f *Function) { - v := 0 +func numberNodes(f *Function) { + var base ID for _, b := range f.Blocks { for _, instr := range b.Instrs { - switch instr.(type) { - case Value: - instr.(interface { - setNum(int) - }).setNum(v) - v++ + if instr == nil { + continue } + base++ + instr.setID(base) } } } @@ -303,17 +384,164 @@ func buildReferrers(f *Function) { } } -// finishBody() finalizes the function after SSA code generation of its body. +func (f *Function) emitConsts() { + if len(f.Blocks) == 0 { + f.consts = nil + return + } + + // TODO(dh): our deduplication only works on booleans and + // integers. other constants are represented as pointers to + // things. + if len(f.consts) == 0 { + return + } else if len(f.consts) <= 32 { + f.emitConstsFew() + } else { + f.emitConstsMany() + } +} + +func (f *Function) emitConstsFew() { + dedup := make([]*Const, 0, 32) + for _, c := range f.consts { + if len(*c.Referrers()) == 0 { + continue + } + found := false + for _, d := range dedup { + if c.typ == d.typ && c.Value == d.Value { + replaceAll(c, d) + found = true + break + } + } + if !found { + dedup = append(dedup, c) + } + } + + instrs := make([]Instruction, len(f.Blocks[0].Instrs)+len(dedup)) + for i, c := range dedup { + instrs[i] = c + c.setBlock(f.Blocks[0]) + } + copy(instrs[len(dedup):], f.Blocks[0].Instrs) + f.Blocks[0].Instrs = instrs + f.consts = nil +} + +func (f *Function) emitConstsMany() { + type constKey struct { + typ types.Type + value constant.Value + } + + m := make(map[constKey]Value, len(f.consts)) + areNil := 0 + for i, c := range f.consts { + if len(*c.Referrers()) == 0 { + f.consts[i] = nil + areNil++ + continue + } + + k := constKey{ + typ: c.typ, + value: c.Value, + } + if dup, ok := m[k]; !ok { + m[k] = c + } else { + f.consts[i] = nil + areNil++ + replaceAll(c, dup) + } + } + + instrs := make([]Instruction, len(f.Blocks[0].Instrs)+len(f.consts)-areNil) + i := 0 + for _, c := range f.consts { + if c != nil { + instrs[i] = c + c.setBlock(f.Blocks[0]) + i++ + } + } + copy(instrs[i:], f.Blocks[0].Instrs) + f.Blocks[0].Instrs = instrs + f.consts = nil +} + +// buildFakeExits ensures that every block in the function is +// reachable in reverse from the Exit block. This is required to build +// a full post-dominator tree, and to ensure the exit block's +// inclusion in the dominator tree. +func buildFakeExits(fn *Function) { + // Find back-edges via forward DFS + fn.fakeExits = BlockSet{values: make([]bool, len(fn.Blocks))} + seen := fn.blockset(0) + backEdges := fn.blockset(1) + + var dfs func(b *BasicBlock) + dfs = func(b *BasicBlock) { + if !seen.Add(b) { + backEdges.Add(b) + return + } + for _, pred := range b.Succs { + dfs(pred) + } + } + dfs(fn.Blocks[0]) +buildLoop: + for { + seen := fn.blockset(2) + var dfs func(b *BasicBlock) + dfs = func(b *BasicBlock) { + if !seen.Add(b) { + return + } + for _, pred := range b.Preds { + dfs(pred) + } + if b == fn.Exit { + for _, b := range fn.Blocks { + if fn.fakeExits.Has(b) { + dfs(b) + } + } + } + } + dfs(fn.Exit) + + for _, b := range fn.Blocks { + if !seen.Has(b) && backEdges.Has(b) { + // Block b is not reachable from the exit block. Add a + // fake jump from b to exit, then try again. Note that we + // only add one fake edge at a time, as it may make + // multiple blocks reachable. + // + // We only consider those blocks that have back edges. + // Any unreachable block that doesn't have a back edge + // must flow into a loop, which by definition has a + // back edge. Thus, by looking for loops, we should + // need fewer fake edges overall. + fn.fakeExits.Add(b) + continue buildLoop + } + } + + break + } +} + +// finishBody() finalizes the function after IR code generation of its body. func (f *Function) finishBody() { f.objects = nil f.currentBlock = nil f.lblocks = nil - // Don't pin the AST in memory (except in debug mode). - if n := f.syntax; n != nil && !f.debugInfo() { - f.syntax = extentNode{n.Pos(), n.End()} - } - // Remove from f.Locals any Allocs that escape to the heap. j := 0 for _, l := range f.Locals { @@ -328,86 +556,25 @@ func (f *Function) finishBody() { } f.Locals = f.Locals[:j] - // comma-ok receiving from a time.Tick channel will never return - // ok == false, so any branching on the value of ok can be - // replaced with an unconditional jump. This will primarily match - // `for range time.Tick(x)` loops, but it can also match - // user-written code. - for _, block := range f.Blocks { - if len(block.Instrs) < 3 { - continue - } - if len(block.Succs) != 2 { - continue - } - var instrs []*Instruction - for i, ins := range block.Instrs { - if _, ok := ins.(*DebugRef); ok { - continue - } - instrs = append(instrs, &block.Instrs[i]) - } - - for i, ins := range instrs { - unop, ok := (*ins).(*UnOp) - if !ok || unop.Op != token.ARROW { - continue - } - call, ok := unop.X.(*Call) - if !ok { - continue - } - if call.Common().IsInvoke() { - continue - } - - // OPT(dh): surely there is a more efficient way of doing - // this, than using FullName. We should already have - // resolved time.Tick somewhere? - v, ok := call.Common().Value.(*Function) - if !ok { - continue - } - t, ok := v.Object().(*types.Func) - if !ok { - continue - } - if t.FullName() != "time.Tick" { - continue - } - ex, ok := (*instrs[i+1]).(*Extract) - if !ok || ex.Tuple != unop || ex.Index != 1 { - continue - } - - ifstmt, ok := (*instrs[i+2]).(*If) - if !ok || ifstmt.Cond != ex { - continue - } - - *instrs[i+2] = NewJump(block) - succ := block.Succs[1] - block.Succs = block.Succs[0:1] - succ.RemovePred(block) - } - } - optimizeBlocks(f) - buildReferrers(f) - buildDomTree(f) + buildPostDomTree(f) if f.Prog.mode&NaiveForm == 0 { - // For debugging pre-state of lifting pass: - // numberRegisters(f) - // f.WriteTo(os.Stderr) lift(f) } - f.namedResults = nil // (used by lifting) + // emit constants after lifting, because lifting may produce new constants. + f.emitConsts() - numberRegisters(f) + f.namedResults = nil // (used by lifting) + f.implicitResults = nil + + numberNodes(f) + + defer f.wr.Close() + f.wr.WriteFunc("start", "start", f) if f.Prog.mode&PrintFunctions != 0 { printMu.Lock() @@ -420,6 +587,29 @@ func (f *Function) finishBody() { } } +func isUselessPhi(phi *Phi) (Value, bool) { + var v0 Value + for _, e := range phi.Edges { + if e == phi { + continue + } + if v0 == nil { + v0 = e + } + if v0 != e { + if v0, ok := v0.(*Const); ok { + if e, ok := e.(*Const); ok { + if v0.typ == e.typ && v0.Value == e.Value { + continue + } + } + } + return nil, false + } + } + return v0, true +} + func (f *Function) RemoveNilBlocks() { f.removeNilBlocks() } @@ -462,26 +652,24 @@ func (f *Function) debugInfo() bool { // returns it. Its name and type are taken from obj. Subsequent // calls to f.lookup(obj) will return the same local. // -func (f *Function) addNamedLocal(obj types.Object) *Alloc { - l := f.addLocal(obj.Type(), obj.Pos()) - l.Comment = obj.Name() +func (f *Function) addNamedLocal(obj types.Object, source ast.Node) *Alloc { + l := f.addLocal(obj.Type(), source) f.objects[obj] = l return l } func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc { - return f.addNamedLocal(f.Pkg.info.Defs[id]) + return f.addNamedLocal(f.Pkg.info.Defs[id], id) } // addLocal creates an anonymous local variable of type typ, adds it // to function f and returns it. pos is the optional source location. // -func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc { +func (f *Function) addLocal(typ types.Type, source ast.Node) *Alloc { v := &Alloc{} v.setType(types.NewPointer(typ)) - v.setPos(pos) f.Locals = append(f.Locals, v) - f.emit(v) + f.emit(v, source) return v } @@ -501,13 +689,12 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value { // Definition must be in an enclosing function; // plumb it through intervening closures. if f.parent == nil { - panic("no ssa.Value for " + obj.String()) + panic("no ir.Value for " + obj.String()) } outer := f.parent.lookup(obj, true) // escaping v := &FreeVar{ name: obj.Name(), typ: outer.Type(), - pos: outer.Pos(), outer: outer, parent: f, } @@ -517,8 +704,8 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value { } // emit emits the specified instruction to function f. -func (f *Function) emit(instr Instruction) Value { - return f.currentBlock.emit(instr) +func (f *Function) emit(instr Instruction, source ast.Node) Value { + return f.currentBlock.emit(instr, source) } // RelString returns the full name of this function, qualified by @@ -637,10 +824,6 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name()) } - if f.Recover != nil { - fmt.Fprintf(buf, "# Recover: %s\n", f.Recover) - } - from := f.pkg() if f.FreeVars != nil { @@ -663,45 +846,38 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { buf.WriteString("\t(external)\n") } - // NB. column calculations are confused by non-ASCII - // characters and assume 8-space tabs. - const punchcard = 80 // for old time's sake. - const tabwidth = 8 for _, b := range f.Blocks { if b == nil { // Corrupt CFG. fmt.Fprintf(buf, ".nil:\n") continue } - n, _ := fmt.Fprintf(buf, "%d:", b.Index) - bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs)) - fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg) + fmt.Fprintf(buf, "b%d:", b.Index) + if len(b.Preds) > 0 { + fmt.Fprint(buf, " ←") + for _, pred := range b.Preds { + fmt.Fprintf(buf, " b%d", pred.Index) + } + } + if b.Comment != "" { + fmt.Fprintf(buf, " # %s", b.Comment) + } + buf.WriteByte('\n') if false { // CFG debugging fmt.Fprintf(buf, "\t# CFG: %s --> %s --> %s\n", b.Preds, b, b.Succs) } + + buf2 := &bytes.Buffer{} for _, instr := range b.Instrs { buf.WriteString("\t") switch v := instr.(type) { case Value: - l := punchcard - tabwidth // Left-align the instruction. if name := v.Name(); name != "" { - n, _ := fmt.Fprintf(buf, "%s = ", name) - l -= n - } - n, _ := buf.WriteString(instr.String()) - l -= n - // Right-align the type if there's space. - if t := v.Type(); t != nil { - buf.WriteByte(' ') - ts := relType(t, from) - l -= len(ts) + len(" ") // (spaces before and after type) - if l > 0 { - fmt.Fprintf(buf, "%*s", l, "") - } - buf.WriteString(ts) + fmt.Fprintf(buf, "%s = ", name) } + buf.WriteString(instr.String()) case nil: // Be robust against bad transforms. buf.WriteString("") @@ -709,9 +885,30 @@ func WriteFunction(buf *bytes.Buffer, f *Function) { buf.WriteString(instr.String()) } buf.WriteString("\n") + + if f.Prog.mode&PrintSource != 0 { + if s := instr.Source(); s != nil { + buf2.Reset() + format.Node(buf2, f.Prog.Fset, s) + for { + line, err := buf2.ReadString('\n') + if len(line) == 0 { + break + } + buf.WriteString("\t\t> ") + buf.WriteString(line) + if line[len(line)-1] != '\n' { + buf.WriteString("\n") + } + if err != nil { + break + } + } + } + } } + buf.WriteString("\n") } - fmt.Fprintf(buf, "\n") } // newBasicBlock adds to f a new basic block and returns it. It does @@ -736,7 +933,7 @@ func (f *Function) newBasicBlock(comment string) *BasicBlock { // the function object, e.g. Pkg, Params, Blocks. // // It is practically impossible for clients to construct well-formed -// SSA functions/packages/programs directly, so we assume this is the +// IR functions/packages/programs directly, so we assume this is the // job of the Builder alone. NewFunction exists to provide clients a // little flexibility. For example, analysis tools may wish to // construct fake Functions for the root of the callgraph, a fake @@ -748,18 +945,17 @@ func (prog *Program) NewFunction(name string, sig *types.Signature, provenance s return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance} } +//lint:ignore U1000 we may make use of this for functions loaded from export data type extentNode [2]token.Pos func (n extentNode) Pos() token.Pos { return n[0] } func (n extentNode) End() token.Pos { return n[1] } -// Syntax returns an ast.Node whose Pos/End methods provide the -// lexical extent of the function if it was defined by Go source code -// (f.Synthetic==""), or nil otherwise. -// -// If f was built with debug information (see Package.SetDebugRef), -// the result is the *ast.FuncDecl or *ast.FuncLit that declared the -// function. Otherwise, it is an opaque Node providing only position -// information; this avoids pinning the AST in memory. -// -func (f *Function) Syntax() ast.Node { return f.syntax } +func (f *Function) initHTML(name string) { + if name == "" { + return + } + if rel := f.RelString(nil); rel == name { + f.wr = NewHTMLWriter("ir.html", rel, "") + } +} diff --git a/vendor/honnef.co/go/tools/ir/html.go b/vendor/honnef.co/go/tools/ir/html.go new file mode 100644 index 0000000000..c18375333a --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/html.go @@ -0,0 +1,1124 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Copyright 2019 Dominik Honnef. All rights reserved. + +package ir + +import ( + "bytes" + "fmt" + "go/types" + "html" + "io" + "log" + "os" + "os/exec" + "path/filepath" + "reflect" + "sort" + "strings" +) + +func live(f *Function) []bool { + max := 0 + var ops []*Value + + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + if int(instr.ID()) > max { + max = int(instr.ID()) + } + } + } + + out := make([]bool, max+1) + var q []Node + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + switch instr.(type) { + case *BlankStore, *Call, *ConstantSwitch, *Defer, *Go, *If, *Jump, *MapUpdate, *Next, *Panic, *Recv, *Return, *RunDefers, *Send, *Store, *Unreachable: + out[instr.ID()] = true + q = append(q, instr) + } + } + } + + for len(q) > 0 { + v := q[len(q)-1] + q = q[:len(q)-1] + for _, op := range v.Operands(ops) { + if *op == nil { + continue + } + if !out[(*op).ID()] { + out[(*op).ID()] = true + q = append(q, *op) + } + } + } + + return out +} + +type funcPrinter interface { + startBlock(b *BasicBlock, reachable bool) + endBlock(b *BasicBlock) + value(v Node, live bool) + startDepCycle() + endDepCycle() + named(n string, vals []Value) +} + +func namedValues(f *Function) map[types.Object][]Value { + names := map[types.Object][]Value{} + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + if instr, ok := instr.(*DebugRef); ok { + if obj := instr.object; obj != nil { + names[obj] = append(names[obj], instr.X) + } + } + } + } + // XXX deduplicate values + return names +} + +func fprintFunc(p funcPrinter, f *Function) { + // XXX does our IR form preserve unreachable blocks? + // reachable, live := findlive(f) + + l := live(f) + for _, b := range f.Blocks { + // XXX + // p.startBlock(b, reachable[b.Index]) + p.startBlock(b, true) + + end := len(b.Instrs) - 1 + if end < 0 { + end = 0 + } + for _, v := range b.Instrs[:end] { + if _, ok := v.(*DebugRef); !ok { + p.value(v, l[v.ID()]) + } + } + p.endBlock(b) + } + + names := namedValues(f) + keys := make([]types.Object, 0, len(names)) + for key := range names { + keys = append(keys, key) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i].Pos() < keys[j].Pos() + }) + for _, key := range keys { + p.named(key.Name(), names[key]) + } +} + +func opName(v Node) string { + switch v := v.(type) { + case *Call: + if v.Common().IsInvoke() { + return "Invoke" + } + return "Call" + case *Alloc: + if v.Heap { + return "HeapAlloc" + } + return "StackAlloc" + case *Select: + if v.Blocking { + return "SelectBlocking" + } + return "SelectNonBlocking" + default: + return reflect.ValueOf(v).Type().Elem().Name() + } +} + +type HTMLWriter struct { + w io.WriteCloser + path string + dot *dotWriter +} + +func NewHTMLWriter(path string, funcname, cfgMask string) *HTMLWriter { + out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + log.Fatalf("%v", err) + } + pwd, err := os.Getwd() + if err != nil { + log.Fatalf("%v", err) + } + html := HTMLWriter{w: out, path: filepath.Join(pwd, path)} + html.dot = newDotWriter() + html.start(funcname) + return &html +} + +func (w *HTMLWriter) start(name string) { + if w == nil { + return + } + w.WriteString("") + w.WriteString(` + + + + + +`) + w.WriteString("") + w.WriteString("

") + w.WriteString(html.EscapeString(name)) + w.WriteString("

") + w.WriteString(` +help +
+ +

+Click on a value or block to toggle highlighting of that value/block +and its uses. (Values and blocks are highlighted by ID, and IDs of +dead items may be reused, so not all highlights necessarily correspond +to the clicked item.) +

+ +

+Faded out values and blocks are dead code that has not been eliminated. +

+ +

+Values printed in italics have a dependency cycle. +

+ +

+CFG: Dashed edge is for unlikely branches. Blue color is for backward edges. +Edge with a dot means that this edge follows the order in which blocks were laidout. +

+ +
+`) + w.WriteString("") + w.WriteString("") +} + +func (w *HTMLWriter) Close() { + if w == nil { + return + } + io.WriteString(w.w, "") + io.WriteString(w.w, "
") + io.WriteString(w.w, "") + io.WriteString(w.w, "") + w.w.Close() + fmt.Printf("dumped IR to %v\n", w.path) +} + +// WriteFunc writes f in a column headed by title. +// phase is used for collapsing columns and should be unique across the table. +func (w *HTMLWriter) WriteFunc(phase, title string, f *Function) { + if w == nil { + return + } + w.WriteColumn(phase, title, "", funcHTML(f, phase, w.dot)) +} + +// WriteColumn writes raw HTML in a column headed by title. +// It is intended for pre- and post-compilation log output. +func (w *HTMLWriter) WriteColumn(phase, title, class, html string) { + if w == nil { + return + } + id := strings.Replace(phase, " ", "-", -1) + // collapsed column + w.Printf("
%v
", id, phase) + + if class == "" { + w.Printf("", id) + } else { + w.Printf("", id, class) + } + w.WriteString("

" + title + "

") + w.WriteString(html) + w.WriteString("") +} + +func (w *HTMLWriter) Printf(msg string, v ...interface{}) { + if _, err := fmt.Fprintf(w.w, msg, v...); err != nil { + log.Fatalf("%v", err) + } +} + +func (w *HTMLWriter) WriteString(s string) { + if _, err := io.WriteString(w.w, s); err != nil { + log.Fatalf("%v", err) + } +} + +func valueHTML(v Node) string { + if v == nil { + return "<nil>" + } + // TODO: Using the value ID as the class ignores the fact + // that value IDs get recycled and that some values + // are transmuted into other values. + class := fmt.Sprintf("t%d", v.ID()) + var label string + switch v := v.(type) { + case *Function: + label = v.RelString(nil) + case *Builtin: + label = v.Name() + default: + label = class + } + return fmt.Sprintf("%s", class, label) +} + +func valueLongHTML(v Node) string { + // TODO: Any intra-value formatting? + // I'm wary of adding too much visual noise, + // but a little bit might be valuable. + // We already have visual noise in the form of punctuation + // maybe we could replace some of that with formatting. + s := fmt.Sprintf("", v.ID()) + + linenumber := "(?)" + if v.Pos().IsValid() { + line := v.Parent().Prog.Fset.Position(v.Pos()).Line + linenumber = fmt.Sprintf("(%d)", line, line) + } + + s += fmt.Sprintf("%s %s = %s", valueHTML(v), linenumber, opName(v)) + + if v, ok := v.(Value); ok { + s += " <" + html.EscapeString(v.Type().String()) + ">" + } + + switch v := v.(type) { + case *Parameter: + s += fmt.Sprintf(" {%s}", html.EscapeString(v.name)) + case *BinOp: + s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String())) + case *UnOp: + s += fmt.Sprintf(" {%s}", html.EscapeString(v.Op.String())) + case *Extract: + name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() + s += fmt.Sprintf(" [%d] (%s)", v.Index, name) + case *Field: + st := v.X.Type().Underlying().(*types.Struct) + // Be robust against a bad index. + name := "?" + if 0 <= v.Field && v.Field < st.NumFields() { + name = st.Field(v.Field).Name() + } + s += fmt.Sprintf(" [%d] (%s)", v.Field, name) + case *FieldAddr: + st := deref(v.X.Type()).Underlying().(*types.Struct) + // Be robust against a bad index. + name := "?" + if 0 <= v.Field && v.Field < st.NumFields() { + name = st.Field(v.Field).Name() + } + + s += fmt.Sprintf(" [%d] (%s)", v.Field, name) + case *Recv: + s += fmt.Sprintf(" {%t}", v.CommaOk) + case *Call: + if v.Common().IsInvoke() { + s += fmt.Sprintf(" {%s}", html.EscapeString(v.Common().Method.FullName())) + } + case *Const: + if v.Value == nil { + s += " {<nil>}" + } else { + s += fmt.Sprintf(" {%s}", html.EscapeString(v.Value.String())) + } + case *Sigma: + s += fmt.Sprintf(" [#%s]", v.From) + } + for _, a := range v.Operands(nil) { + s += fmt.Sprintf(" %s", valueHTML(*a)) + } + + // OPT(dh): we're calling namedValues many times on the same function. + allNames := namedValues(v.Parent()) + var names []string + for name, values := range allNames { + for _, value := range values { + if v == value { + names = append(names, name.Name()) + break + } + } + } + if len(names) != 0 { + s += " (" + strings.Join(names, ", ") + ")" + } + + s += "" + return s +} + +func blockHTML(b *BasicBlock) string { + // TODO: Using the value ID as the class ignores the fact + // that value IDs get recycled and that some values + // are transmuted into other values. + s := html.EscapeString(b.String()) + return fmt.Sprintf("%s", s, s) +} + +func blockLongHTML(b *BasicBlock) string { + var kind string + var term Instruction + if len(b.Instrs) > 0 { + term = b.Control() + kind = opName(term) + } + // TODO: improve this for HTML? + s := fmt.Sprintf("%s", b.Index, kind) + + if term != nil { + ops := term.Operands(nil) + if len(ops) > 0 { + var ss []string + for _, op := range ops { + ss = append(ss, valueHTML(*op)) + } + s += " " + strings.Join(ss, ", ") + } + } + if len(b.Succs) > 0 { + s += " →" // right arrow + for _, c := range b.Succs { + s += " " + blockHTML(c) + } + } + return s +} + +func funcHTML(f *Function, phase string, dot *dotWriter) string { + buf := new(bytes.Buffer) + if dot != nil { + dot.writeFuncSVG(buf, phase, f) + } + fmt.Fprint(buf, "") + p := htmlFuncPrinter{w: buf} + fprintFunc(p, f) + + // fprintFunc(&buf, f) // TODO: HTML, not text,
for line breaks, etc. + fmt.Fprint(buf, "
") + return buf.String() +} + +type htmlFuncPrinter struct { + w io.Writer +} + +func (p htmlFuncPrinter) startBlock(b *BasicBlock, reachable bool) { + var dead string + if !reachable { + dead = "dead-block" + } + fmt.Fprintf(p.w, "
    ", b, dead) + fmt.Fprintf(p.w, "
  • %s:", blockHTML(b)) + if len(b.Preds) > 0 { + io.WriteString(p.w, " ←") // left arrow + for _, pred := range b.Preds { + fmt.Fprintf(p.w, " %s", blockHTML(pred)) + } + } + if len(b.Instrs) > 0 { + io.WriteString(p.w, ``) + } + io.WriteString(p.w, "
  • ") + if len(b.Instrs) > 0 { // start list of values + io.WriteString(p.w, "
  • ") + io.WriteString(p.w, "
      ") + } +} + +func (p htmlFuncPrinter) endBlock(b *BasicBlock) { + if len(b.Instrs) > 0 { // end list of values + io.WriteString(p.w, "
    ") + io.WriteString(p.w, "
  • ") + } + io.WriteString(p.w, "
  • ") + fmt.Fprint(p.w, blockLongHTML(b)) + io.WriteString(p.w, "
  • ") + io.WriteString(p.w, "
") +} + +func (p htmlFuncPrinter) value(v Node, live bool) { + var dead string + if !live { + dead = "dead-value" + } + fmt.Fprintf(p.w, "
  • ", dead) + fmt.Fprint(p.w, valueLongHTML(v)) + io.WriteString(p.w, "
  • ") +} + +func (p htmlFuncPrinter) startDepCycle() { + fmt.Fprintln(p.w, "") +} + +func (p htmlFuncPrinter) endDepCycle() { + fmt.Fprintln(p.w, "") +} + +func (p htmlFuncPrinter) named(n string, vals []Value) { + fmt.Fprintf(p.w, "
  • name %s: ", n) + for _, val := range vals { + fmt.Fprintf(p.w, "%s ", valueHTML(val)) + } + fmt.Fprintf(p.w, "
  • ") +} + +type dotWriter struct { + path string + broken bool +} + +// newDotWriter returns non-nil value when mask is valid. +// dotWriter will generate SVGs only for the phases specified in the mask. +// mask can contain following patterns and combinations of them: +// * - all of them; +// x-y - x through y, inclusive; +// x,y - x and y, but not the passes between. +func newDotWriter() *dotWriter { + path, err := exec.LookPath("dot") + if err != nil { + fmt.Println(err) + return nil + } + return &dotWriter{path: path} +} + +func (d *dotWriter) writeFuncSVG(w io.Writer, phase string, f *Function) { + if d.broken { + return + } + cmd := exec.Command(d.path, "-Tsvg") + pipe, err := cmd.StdinPipe() + if err != nil { + d.broken = true + fmt.Println(err) + return + } + buf := new(bytes.Buffer) + cmd.Stdout = buf + bufErr := new(bytes.Buffer) + cmd.Stderr = bufErr + err = cmd.Start() + if err != nil { + d.broken = true + fmt.Println(err) + return + } + fmt.Fprint(pipe, `digraph "" { margin=0; size="4,40"; ranksep=.2; `) + id := strings.Replace(phase, " ", "-", -1) + fmt.Fprintf(pipe, `id="g_graph_%s";`, id) + fmt.Fprintf(pipe, `node [style=filled,fillcolor=white,fontsize=16,fontname="Menlo,Times,serif",margin="0.01,0.03"];`) + fmt.Fprintf(pipe, `edge [fontsize=16,fontname="Menlo,Times,serif"];`) + for _, b := range f.Blocks { + layout := "" + fmt.Fprintf(pipe, `%v [label="%v%s\n%v",id="graph_node_%v_%v"];`, b, b, layout, b.Control().String(), id, b) + } + indexOf := make([]int, len(f.Blocks)) + for i, b := range f.Blocks { + indexOf[b.Index] = i + } + + // XXX + /* + ponums := make([]int32, len(f.Blocks)) + _ = postorderWithNumbering(f, ponums) + isBackEdge := func(from, to int) bool { + return ponums[from] <= ponums[to] + } + */ + isBackEdge := func(from, to int) bool { return false } + + for _, b := range f.Blocks { + for i, s := range b.Succs { + style := "solid" + color := "black" + arrow := "vee" + if isBackEdge(b.Index, s.Index) { + color = "blue" + } + fmt.Fprintf(pipe, `%v -> %v [label=" %d ",style="%s",color="%s",arrowhead="%s"];`, b, s, i, style, color, arrow) + } + } + fmt.Fprint(pipe, "}") + pipe.Close() + err = cmd.Wait() + if err != nil { + d.broken = true + fmt.Printf("dot: %v\n%v\n", err, bufErr.String()) + return + } + + svgID := "svg_graph_" + id + fmt.Fprintf(w, `
    `, svgID, svgID) + // For now, an awful hack: edit the html as it passes through + // our fingers, finding ' 0 { + fset = initial[0].Fset + } + + prog := ir.NewProgram(fset, mode) + if opts != nil { + prog.PrintFunc = opts.PrintFunc + } + + isInitial := make(map[*packages.Package]bool, len(initial)) + for _, p := range initial { + isInitial[p] = true + } + + irmap := make(map[*packages.Package]*ir.Package) + packages.Visit(initial, nil, func(p *packages.Package) { + if p.Types != nil && !p.IllTyped { + var files []*ast.File + if deps || isInitial[p] { + files = p.Syntax + } + irmap[p] = prog.CreatePackage(p.Types, files, p.TypesInfo, true) + } + }) + + var irpkgs []*ir.Package + for _, p := range initial { + irpkgs = append(irpkgs, irmap[p]) // may be nil + } + return prog, irpkgs +} + +// CreateProgram returns a new program in IR form, given a program +// loaded from source. An IR package is created for each transitively +// error-free package of lprog. +// +// Code for bodies of functions is not built until Build is called +// on the result. +// +// The mode parameter controls diagnostics and checking during IR construction. +// +// Deprecated: use golang.org/x/tools/go/packages and the Packages +// function instead; see ir.ExampleLoadPackages. +// +func CreateProgram(lprog *loader.Program, mode ir.BuilderMode) *ir.Program { + prog := ir.NewProgram(lprog.Fset, mode) + + for _, info := range lprog.AllPackages { + if info.TransitivelyErrorFree { + prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) + } + } + + return prog +} + +// BuildPackage builds an IR program with IR for a single package. +// +// It populates pkg by type-checking the specified file ASTs. All +// dependencies are loaded using the importer specified by tc, which +// typically loads compiler export data; IR code cannot be built for +// those packages. BuildPackage then constructs an ir.Program with all +// dependency packages created, and builds and returns the IR package +// corresponding to pkg. +// +// The caller must have set pkg.Path() to the import path. +// +// The operation fails if there were any type-checking or import errors. +// +// See ../ir/example_test.go for an example. +// +func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ir.BuilderMode) (*ir.Package, *types.Info, error) { + if fset == nil { + panic("no token.FileSet") + } + if pkg.Path() == "" { + panic("package has no import path") + } + + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + } + if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil { + return nil, nil, err + } + + prog := ir.NewProgram(fset, mode) + + // Create IR packages for all imports. + // Order is not significant. + created := make(map[*types.Package]bool) + var createAll func(pkgs []*types.Package) + createAll = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !created[p] { + created[p] = true + prog.CreatePackage(p, nil, nil, true) + createAll(p.Imports()) + } + } + } + createAll(pkg.Imports()) + + // Create and build the primary package. + irpkg := prog.CreatePackage(pkg, files, info, false) + irpkg.Build() + return irpkg, info, nil +} diff --git a/vendor/honnef.co/go/tools/ir/irutil/switch.go b/vendor/honnef.co/go/tools/ir/irutil/switch.go new file mode 100644 index 0000000000..f44cbca9e9 --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/irutil/switch.go @@ -0,0 +1,264 @@ +// Copyright 2013 The Go 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 irutil + +// This file implements discovery of switch and type-switch constructs +// from low-level control flow. +// +// Many techniques exist for compiling a high-level switch with +// constant cases to efficient machine code. The optimal choice will +// depend on the data type, the specific case values, the code in the +// body of each case, and the hardware. +// Some examples: +// - a lookup table (for a switch that maps constants to constants) +// - a computed goto +// - a binary tree +// - a perfect hash +// - a two-level switch (to partition constant strings by their first byte). + +import ( + "bytes" + "fmt" + "go/token" + "go/types" + + "honnef.co/go/tools/ir" +) + +// A ConstCase represents a single constant comparison. +// It is part of a Switch. +type ConstCase struct { + Block *ir.BasicBlock // block performing the comparison + Body *ir.BasicBlock // body of the case + Value *ir.Const // case comparand +} + +// A TypeCase represents a single type assertion. +// It is part of a Switch. +type TypeCase struct { + Block *ir.BasicBlock // block performing the type assert + Body *ir.BasicBlock // body of the case + Type types.Type // case type + Binding ir.Value // value bound by this case +} + +// A Switch is a logical high-level control flow operation +// (a multiway branch) discovered by analysis of a CFG containing +// only if/else chains. It is not part of the ir.Instruction set. +// +// One of ConstCases and TypeCases has length >= 2; +// the other is nil. +// +// In a value switch, the list of cases may contain duplicate constants. +// A type switch may contain duplicate types, or types assignable +// to an interface type also in the list. +// TODO(adonovan): eliminate such duplicates. +// +type Switch struct { + Start *ir.BasicBlock // block containing start of if/else chain + X ir.Value // the switch operand + ConstCases []ConstCase // ordered list of constant comparisons + TypeCases []TypeCase // ordered list of type assertions + Default *ir.BasicBlock // successor if all comparisons fail +} + +func (sw *Switch) String() string { + // We represent each block by the String() of its + // first Instruction, e.g. "print(42:int)". + var buf bytes.Buffer + if sw.ConstCases != nil { + fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) + for _, c := range sw.ConstCases { + fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0]) + } + } else { + fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) + for _, c := range sw.TypeCases { + fmt.Fprintf(&buf, "case %s %s: %s\n", + c.Binding.Name(), c.Type, c.Body.Instrs[0]) + } + } + if sw.Default != nil { + fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) + } + fmt.Fprintf(&buf, "}") + return buf.String() +} + +// Switches examines the control-flow graph of fn and returns the +// set of inferred value and type switches. A value switch tests an +// ir.Value for equality against two or more compile-time constant +// values. Switches involving link-time constants (addresses) are +// ignored. A type switch type-asserts an ir.Value against two or +// more types. +// +// The switches are returned in dominance order. +// +// The resulting switches do not necessarily correspond to uses of the +// 'switch' keyword in the source: for example, a single source-level +// switch statement with non-constant cases may result in zero, one or +// many Switches, one per plural sequence of constant cases. +// Switches may even be inferred from if/else- or goto-based control flow. +// (In general, the control flow constructs of the source program +// cannot be faithfully reproduced from the IR.) +// +func Switches(fn *ir.Function) []Switch { + // Traverse the CFG in dominance order, so we don't + // enter an if/else-chain in the middle. + var switches []Switch + seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet + for _, b := range fn.DomPreorder() { + if x, k := isComparisonBlock(b); x != nil { + // Block b starts a switch. + sw := Switch{Start: b, X: x} + valueSwitch(&sw, k, seen) + if len(sw.ConstCases) > 1 { + switches = append(switches, sw) + } + } + + if y, x, T := isTypeAssertBlock(b); y != nil { + // Block b starts a type switch. + sw := Switch{Start: b, X: x} + typeSwitch(&sw, y, T, seen) + if len(sw.TypeCases) > 1 { + switches = append(switches, sw) + } + } + } + return switches +} + +func isSameX(x1 ir.Value, x2 ir.Value) bool { + if x1 == x2 { + return true + } + if x2, ok := x2.(*ir.Sigma); ok { + return isSameX(x1, x2.X) + } + return false +} + +func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { + b := sw.Start + x := sw.X + for isSameX(sw.X, x) { + if seen[b] { + break + } + seen[b] = true + + sw.ConstCases = append(sw.ConstCases, ConstCase{ + Block: b, + Body: b.Succs[0], + Value: k, + }) + b = b.Succs[1] + n := 0 + for _, instr := range b.Instrs { + switch instr.(type) { + case *ir.If, *ir.BinOp: + n++ + case *ir.Sigma, *ir.Phi, *ir.DebugRef: + default: + n += 1000 + } + } + if n != 2 { + // Block b contains not just 'if x == k' and σ/ϕ nodes, + // so it may have side effects that + // make it unsafe to elide. + break + } + if len(b.Preds) != 1 { + // Block b has multiple predecessors, + // so it cannot be treated as a case. + break + } + x, k = isComparisonBlock(b) + } + sw.Default = b +} + +func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) { + b := sw.Start + x := sw.X + for isSameX(sw.X, x) { + if seen[b] { + break + } + seen[b] = true + + sw.TypeCases = append(sw.TypeCases, TypeCase{ + Block: b, + Body: b.Succs[0], + Type: T, + Binding: y, + }) + b = b.Succs[1] + n := 0 + for _, instr := range b.Instrs { + switch instr.(type) { + case *ir.TypeAssert, *ir.Extract, *ir.If: + n++ + case *ir.Sigma, *ir.Phi: + default: + n += 1000 + } + } + if n != 4 { + // Block b contains not just + // {TypeAssert; Extract #0; Extract #1; If} + // so it may have side effects that + // make it unsafe to elide. + break + } + if len(b.Preds) != 1 { + // Block b has multiple predecessors, + // so it cannot be treated as a case. + break + } + y, x, T = isTypeAssertBlock(b) + } + sw.Default = b +} + +// isComparisonBlock returns the operands (v, k) if a block ends with +// a comparison v==k, where k is a compile-time constant. +// +func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) { + if n := len(b.Instrs); n >= 2 { + if i, ok := b.Instrs[n-1].(*ir.If); ok { + if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { + if k, ok := binop.Y.(*ir.Const); ok { + return binop.X, k + } + if k, ok := binop.X.(*ir.Const); ok { + return binop.Y, k + } + } + } + } + return +} + +// isTypeAssertBlock returns the operands (y, x, T) if a block ends with +// a type assertion "if y, ok := x.(T); ok {". +// +func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) { + if n := len(b.Instrs); n >= 4 { + if i, ok := b.Instrs[n-1].(*ir.If); ok { + if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 { + if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b { + // hack: relies upon instruction ordering. + if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok { + return ext0, ta.X, ta.AssertedType + } + } + } + } + } + return +} diff --git a/vendor/honnef.co/go/tools/ir/irutil/util.go b/vendor/honnef.co/go/tools/ir/irutil/util.go new file mode 100644 index 0000000000..04b25f5f9b --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/irutil/util.go @@ -0,0 +1,70 @@ +package irutil + +import ( + "honnef.co/go/tools/ir" +) + +func Reachable(from, to *ir.BasicBlock) bool { + if from == to { + return true + } + if from.Dominates(to) { + return true + } + + found := false + Walk(from, func(b *ir.BasicBlock) bool { + if b == to { + found = true + return false + } + return true + }) + return found +} + +func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) { + seen := map[*ir.BasicBlock]bool{} + wl := []*ir.BasicBlock{b} + for len(wl) > 0 { + b := wl[len(wl)-1] + wl = wl[:len(wl)-1] + if seen[b] { + continue + } + seen[b] = true + if !fn(b) { + continue + } + wl = append(wl, b.Succs...) + } +} + +func Vararg(x *ir.Slice) ([]ir.Value, bool) { + var out []ir.Value + slice, ok := x.X.(*ir.Alloc) + if !ok { + return nil, false + } + for _, ref := range *slice.Referrers() { + if ref == x { + continue + } + if ref.Block() != x.Block() { + return nil, false + } + idx, ok := ref.(*ir.IndexAddr) + if !ok { + return nil, false + } + if len(*idx.Referrers()) != 1 { + return nil, false + } + store, ok := (*idx.Referrers())[0].(*ir.Store) + if !ok { + return nil, false + } + out = append(out, store.Val) + } + return out, true +} diff --git a/vendor/honnef.co/go/tools/ir/irutil/visit.go b/vendor/honnef.co/go/tools/ir/irutil/visit.go new file mode 100644 index 0000000000..657c9cde74 --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/irutil/visit.go @@ -0,0 +1,79 @@ +// Copyright 2013 The Go 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 irutil // import "honnef.co/go/tools/ir/irutil" + +import "honnef.co/go/tools/ir" + +// This file defines utilities for visiting the IR of +// a Program. +// +// TODO(adonovan): test coverage. + +// AllFunctions finds and returns the set of functions potentially +// needed by program prog, as determined by a simple linker-style +// reachability algorithm starting from the members and method-sets of +// each package. The result may include anonymous functions and +// synthetic wrappers. +// +// Precondition: all packages are built. +// +func AllFunctions(prog *ir.Program) map[*ir.Function]bool { + visit := visitor{ + prog: prog, + seen: make(map[*ir.Function]bool), + } + visit.program() + return visit.seen +} + +type visitor struct { + prog *ir.Program + seen map[*ir.Function]bool +} + +func (visit *visitor) program() { + for _, pkg := range visit.prog.AllPackages() { + for _, mem := range pkg.Members { + if fn, ok := mem.(*ir.Function); ok { + visit.function(fn) + } + } + } + for _, T := range visit.prog.RuntimeTypes() { + mset := visit.prog.MethodSets.MethodSet(T) + for i, n := 0, mset.Len(); i < n; i++ { + visit.function(visit.prog.MethodValue(mset.At(i))) + } + } +} + +func (visit *visitor) function(fn *ir.Function) { + if !visit.seen[fn] { + visit.seen[fn] = true + var buf [10]*ir.Value // avoid alloc in common case + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + for _, op := range instr.Operands(buf[:0]) { + if fn, ok := (*op).(*ir.Function); ok { + visit.function(fn) + } + } + } + } + } +} + +// MainPackages returns the subset of the specified packages +// named "main" that define a main function. +// The result may include synthetic "testmain" packages. +func MainPackages(pkgs []*ir.Package) []*ir.Package { + var mains []*ir.Package + for _, pkg := range pkgs { + if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { + mains = append(mains, pkg) + } + } + return mains +} diff --git a/vendor/honnef.co/go/tools/ir/lift.go b/vendor/honnef.co/go/tools/ir/lift.go new file mode 100644 index 0000000000..71d5c8cb06 --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/lift.go @@ -0,0 +1,1063 @@ +// Copyright 2013 The Go 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 + +// This file defines the lifting pass which tries to "lift" Alloc +// cells (new/local variables) into SSA registers, replacing loads +// with the dominating stored value, eliminating loads and stores, and +// inserting φ- and σ-nodes as needed. + +// Cited papers and resources: +// +// Ron Cytron et al. 1991. Efficiently computing SSA form... +// http://doi.acm.org/10.1145/115372.115320 +// +// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm. +// Software Practice and Experience 2001, 4:1-10. +// http://www.hipersoft.rice.edu/grads/publications/dom14.pdf +// +// Daniel Berlin, llvmdev mailing list, 2012. +// http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html +// (Be sure to expand the whole thread.) +// +// C. Scott Ananian. 1997. The static single information form. +// +// Jeremy Singer. 2006. Static program analysis based on virtual register renaming. + +// TODO(adonovan): opt: there are many optimizations worth evaluating, and +// the conventional wisdom for SSA construction is that a simple +// algorithm well engineered often beats those of better asymptotic +// complexity on all but the most egregious inputs. +// +// Danny Berlin suggests that the Cooper et al. algorithm for +// computing the dominance frontier is superior to Cytron et al. +// Furthermore he recommends that rather than computing the DF for the +// whole function then renaming all alloc cells, it may be cheaper to +// compute the DF for each alloc cell separately and throw it away. +// +// Consider exploiting liveness information to avoid creating dead +// φ-nodes which we then immediately remove. +// +// Also see many other "TODO: opt" suggestions in the code. + +import ( + "fmt" + "go/types" + "os" +) + +// If true, show diagnostic information at each step of lifting. +// Very verbose. +const debugLifting = false + +// domFrontier maps each block to the set of blocks in its dominance +// frontier. The outer slice is conceptually a map keyed by +// Block.Index. The inner slice is conceptually a set, possibly +// containing duplicates. +// +// TODO(adonovan): opt: measure impact of dups; consider a packed bit +// representation, e.g. big.Int, and bitwise parallel operations for +// the union step in the Children loop. +// +// domFrontier's methods mutate the slice's elements but not its +// length, so their receivers needn't be pointers. +// +type domFrontier [][]*BasicBlock + +func (df domFrontier) add(u, v *BasicBlock) { + df[u.Index] = append(df[u.Index], v) +} + +// build builds the dominance frontier df for the dominator tree of +// fn, using the algorithm found in A Simple, Fast Dominance +// Algorithm, Figure 5. +// +// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA +// by pruning the entire IDF computation, rather than merely pruning +// the DF -> IDF step. +func (df domFrontier) build(fn *Function) { + for _, b := range fn.Blocks { + if len(b.Preds) >= 2 { + for _, p := range b.Preds { + runner := p + for runner != b.dom.idom { + df.add(runner, b) + runner = runner.dom.idom + } + } + } + } +} + +func buildDomFrontier(fn *Function) domFrontier { + df := make(domFrontier, len(fn.Blocks)) + df.build(fn) + return df +} + +type postDomFrontier [][]*BasicBlock + +func (rdf postDomFrontier) add(u, v *BasicBlock) { + rdf[u.Index] = append(rdf[u.Index], v) +} + +func (rdf postDomFrontier) build(fn *Function) { + for _, b := range fn.Blocks { + if len(b.Succs) >= 2 { + for _, s := range b.Succs { + runner := s + for runner != b.pdom.idom { + rdf.add(runner, b) + runner = runner.pdom.idom + } + } + } + } +} + +func buildPostDomFrontier(fn *Function) postDomFrontier { + rdf := make(postDomFrontier, len(fn.Blocks)) + rdf.build(fn) + return rdf +} + +func removeInstr(refs []Instruction, instr Instruction) []Instruction { + i := 0 + for _, ref := range refs { + if ref == instr { + continue + } + refs[i] = ref + i++ + } + for j := i; j != len(refs); j++ { + refs[j] = nil // aid GC + } + return refs[:i] +} + +func clearInstrs(instrs []Instruction) { + for i := range instrs { + instrs[i] = nil + } +} + +// lift replaces local and new Allocs accessed only with +// load/store by IR registers, inserting φ- and σ-nodes where necessary. +// The result is a program in pruned SSI form. +// +// Preconditions: +// - fn has no dead blocks (blockopt has run). +// - Def/use info (Operands and Referrers) is up-to-date. +// - The dominator tree is up-to-date. +// +func lift(fn *Function) { + // TODO(adonovan): opt: lots of little optimizations may be + // worthwhile here, especially if they cause us to avoid + // buildDomFrontier. For example: + // + // - Alloc never loaded? Eliminate. + // - Alloc never stored? Replace all loads with a zero constant. + // - Alloc stored once? Replace loads with dominating store; + // don't forget that an Alloc is itself an effective store + // of zero. + // - Alloc used only within a single block? + // Use degenerate algorithm avoiding φ-nodes. + // - Consider synergy with scalar replacement of aggregates (SRA). + // e.g. *(&x.f) where x is an Alloc. + // Perhaps we'd get better results if we generated this as x.f + // i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)). + // Unclear. + // + // But we will start with the simplest correct code. + var df domFrontier + var rdf postDomFrontier + var closure *closure + var newPhis newPhiMap + var newSigmas newSigmaMap + + // During this pass we will replace some BasicBlock.Instrs + // (allocs, loads and stores) with nil, keeping a count in + // BasicBlock.gaps. At the end we will reset Instrs to the + // concatenation of all non-dead newPhis and non-nil Instrs + // for the block, reusing the original array if space permits. + + // While we're here, we also eliminate 'rundefers' + // instructions in functions that contain no 'defer' + // instructions. + usesDefer := false + + // Determine which allocs we can lift and number them densely. + // The renaming phase uses this numbering for compact maps. + numAllocs := 0 + for _, b := range fn.Blocks { + b.gaps = 0 + b.rundefers = 0 + for _, instr := range b.Instrs { + switch instr := instr.(type) { + case *Alloc: + if !liftable(instr) { + instr.index = -1 + continue + } + index := -1 + if numAllocs == 0 { + df = buildDomFrontier(fn) + rdf = buildPostDomFrontier(fn) + if len(fn.Blocks) > 2 { + closure = transitiveClosure(fn) + } + newPhis = make(newPhiMap, len(fn.Blocks)) + newSigmas = make(newSigmaMap, len(fn.Blocks)) + + if debugLifting { + title := false + for i, blocks := range df { + if blocks != nil { + if !title { + fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn) + title = true + } + fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks) + } + } + } + } + liftAlloc(closure, df, rdf, instr, newPhis, newSigmas) + index = numAllocs + numAllocs++ + instr.index = index + case *Defer: + usesDefer = true + case *RunDefers: + b.rundefers++ + } + } + } + + if numAllocs > 0 { + // renaming maps an alloc (keyed by index) to its replacement + // value. Initially the renaming contains nil, signifying the + // zero constant of the appropriate type; we construct the + // Const lazily at most once on each path through the domtree. + // TODO(adonovan): opt: cache per-function not per subtree. + renaming := make([]Value, numAllocs) + + // Renaming. + rename(fn.Blocks[0], renaming, newPhis, newSigmas) + + simplifyPhis(newPhis) + + // Eliminate dead φ- and σ-nodes. + markLiveNodes(fn.Blocks, newPhis, newSigmas) + } + + // Prepend remaining live φ-nodes to each block and possibly kill rundefers. + for _, b := range fn.Blocks { + var head []Instruction + if numAllocs > 0 { + nps := newPhis[b.Index] + head = make([]Instruction, 0, len(nps)) + for _, pred := range b.Preds { + nss := newSigmas[pred.Index] + idx := pred.succIndex(b) + for _, newSigma := range nss { + if sigma := newSigma.sigmas[idx]; sigma != nil && sigma.live { + head = append(head, sigma) + + // we didn't populate referrers before, as most + // sigma nodes will be killed + if refs := sigma.X.Referrers(); refs != nil { + *refs = append(*refs, sigma) + } + } else if sigma != nil { + sigma.block = nil + } + } + } + for _, np := range nps { + if np.phi.live { + head = append(head, np.phi) + } else { + for _, edge := range np.phi.Edges { + if refs := edge.Referrers(); refs != nil { + *refs = removeInstr(*refs, np.phi) + } + } + np.phi.block = nil + } + } + } + + rundefersToKill := b.rundefers + if usesDefer { + rundefersToKill = 0 + } + + j := len(head) + if j+b.gaps+rundefersToKill == 0 { + continue // fast path: no new phis or gaps + } + + // We could do straight copies instead of element-wise copies + // when both b.gaps and rundefersToKill are zero. However, + // that seems to only be the case ~1% of the time, which + // doesn't seem worth the extra branch. + + // Remove dead instructions, add phis and sigmas + ns := len(b.Instrs) + j - b.gaps - rundefersToKill + if ns <= cap(b.Instrs) { + // b.Instrs has enough capacity to store all instructions + + // OPT(dh): check cap vs the actually required space; if + // there is a big enough difference, it may be worth + // allocating a new slice, to avoid pinning memory. + dst := b.Instrs[:cap(b.Instrs)] + i := len(dst) - 1 + for n := len(b.Instrs) - 1; n >= 0; n-- { + instr := dst[n] + if instr == nil { + continue + } + if !usesDefer { + if _, ok := instr.(*RunDefers); ok { + continue + } + } + dst[i] = instr + i-- + } + off := i + 1 - len(head) + // aid GC + clearInstrs(dst[:off]) + dst = dst[off:] + copy(dst, head) + b.Instrs = dst + } else { + // not enough space, so allocate a new slice and copy + // over. + dst := make([]Instruction, ns) + copy(dst, head) + + for _, instr := range b.Instrs { + if instr == nil { + continue + } + if !usesDefer { + if _, ok := instr.(*RunDefers); ok { + continue + } + } + dst[j] = instr + j++ + } + b.Instrs = dst + } + } + + // Remove any fn.Locals that were lifted. + j := 0 + for _, l := range fn.Locals { + if l.index < 0 { + fn.Locals[j] = l + j++ + } + } + // Nil out fn.Locals[j:] to aid GC. + for i := j; i < len(fn.Locals); i++ { + fn.Locals[i] = nil + } + fn.Locals = fn.Locals[:j] +} + +func hasDirectReferrer(instr Instruction) bool { + for _, instr := range *instr.Referrers() { + switch instr.(type) { + case *Phi, *Sigma: + // ignore + default: + return true + } + } + return false +} + +func markLiveNodes(blocks []*BasicBlock, newPhis newPhiMap, newSigmas newSigmaMap) { + // Phi and sigma nodes are considered live if a non-phi, non-sigma + // node uses them. Once we find a node that is live, we mark all + // of its operands as used, too. + for _, npList := range newPhis { + for _, np := range npList { + phi := np.phi + if !phi.live && hasDirectReferrer(phi) { + markLivePhi(phi) + } + } + } + for _, npList := range newSigmas { + for _, np := range npList { + for _, sigma := range np.sigmas { + if sigma != nil && !sigma.live && hasDirectReferrer(sigma) { + markLiveSigma(sigma) + } + } + } + } + // Existing φ-nodes due to && and || operators + // are all considered live (see Go issue 19622). + for _, b := range blocks { + for _, phi := range b.phis() { + markLivePhi(phi.(*Phi)) + } + } +} + +func markLivePhi(phi *Phi) { + phi.live = true + for _, rand := range phi.Edges { + switch rand := rand.(type) { + case *Phi: + if !rand.live { + markLivePhi(rand) + } + case *Sigma: + if !rand.live { + markLiveSigma(rand) + } + } + } +} + +func markLiveSigma(sigma *Sigma) { + sigma.live = true + switch rand := sigma.X.(type) { + case *Phi: + if !rand.live { + markLivePhi(rand) + } + case *Sigma: + if !rand.live { + markLiveSigma(rand) + } + } +} + +// simplifyPhis replaces trivial phis with non-phi alternatives. Phi +// nodes where all edges are identical, or consist of only the phi +// itself and one other value, may be replaced with the value. +func simplifyPhis(newPhis newPhiMap) { + // find all phis that are trivial and can be replaced with a + // non-phi value. run until we reach a fixpoint, because replacing + // a phi may make other phis trivial. + for changed := true; changed; { + changed = false + for _, npList := range newPhis { + for _, np := range npList { + if np.phi.live { + // we're reusing 'live' to mean 'dead' in the context of simplifyPhis + continue + } + if r, ok := isUselessPhi(np.phi); ok { + // useless phi, replace its uses with the + // replacement value. the dead phi pass will clean + // up the phi afterwards. + replaceAll(np.phi, r) + np.phi.live = true + changed = true + } + } + } + } + + for _, npList := range newPhis { + for _, np := range npList { + np.phi.live = false + } + } +} + +type BlockSet struct { + idx int + values []bool + count int +} + +func NewBlockSet(size int) *BlockSet { + return &BlockSet{values: make([]bool, size)} +} + +func (s *BlockSet) Set(s2 *BlockSet) { + copy(s.values, s2.values) + s.count = 0 + for _, v := range s.values { + if v { + s.count++ + } + } +} + +func (s *BlockSet) Num() int { + return s.count +} + +func (s *BlockSet) Has(b *BasicBlock) bool { + if b.Index >= len(s.values) { + return false + } + return s.values[b.Index] +} + +// add adds b to the set and returns true if the set changed. +func (s *BlockSet) Add(b *BasicBlock) bool { + if s.values[b.Index] { + return false + } + s.count++ + s.values[b.Index] = true + s.idx = b.Index + + return true +} + +func (s *BlockSet) Clear() { + for j := range s.values { + s.values[j] = false + } + s.count = 0 +} + +// take removes an arbitrary element from a set s and +// returns its index, or returns -1 if empty. +func (s *BlockSet) Take() int { + // [i, end] + for i := s.idx; i < len(s.values); i++ { + if s.values[i] { + s.values[i] = false + s.idx = i + s.count-- + return i + } + } + + // [start, i) + for i := 0; i < s.idx; i++ { + if s.values[i] { + s.values[i] = false + s.idx = i + s.count-- + return i + } + } + + return -1 +} + +type closure struct { + span []uint32 + reachables []interval +} + +type interval uint32 + +const ( + flagMask = 1 << 31 + numBits = 20 + lengthBits = 32 - numBits - 1 + lengthMask = (1<>numBits + } else { + // large interval + i++ + start = uint32(inv & numMask) + end = uint32(r[i]) + } + if idx >= start && idx <= end { + return true + } + } + return false +} + +func (c closure) reachable(id int) []interval { + return c.reachables[c.span[id]:c.span[id+1]] +} + +func (c closure) walk(current *BasicBlock, b *BasicBlock, visited []bool) { + visited[b.Index] = true + for _, succ := range b.Succs { + if visited[succ.Index] { + continue + } + visited[succ.Index] = true + c.walk(current, succ, visited) + } +} + +func transitiveClosure(fn *Function) *closure { + reachable := make([]bool, len(fn.Blocks)) + c := &closure{} + c.span = make([]uint32, len(fn.Blocks)+1) + + addInterval := func(start, end uint32) { + if l := end - start; l <= 1<= 0 { // store of zero to Alloc cell + // Replace dominated loads by the zero value. + renaming[instr.index] = nil + if debugLifting { + fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) + } + // Delete the Alloc. + u.Instrs[i] = nil + u.gaps++ + } + + case *Store: + if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell + // Replace dominated loads by the stored value. + renaming[alloc.index] = instr.Val + if debugLifting { + fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", + instr, instr.Val.Name()) + } + if refs := instr.Addr.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + if refs := instr.Val.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + // Delete the Store. + u.Instrs[i] = nil + u.gaps++ + } + + case *Load: + if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell + // In theory, we wouldn't be able to replace loads + // directly, because a loaded value could be used in + // different branches, in which case it should be + // replaced with different sigma nodes. But we can't + // simply defer replacement, either, because then + // later stores might incorrectly affect this load. + // + // To avoid doing renaming on _all_ values (instead of + // just loads and stores like we're doing), we make + // sure during code generation that each load is only + // used in one block. For example, in constant switch + // statements, where the tag is only evaluated once, + // we store it in a temporary and load it for each + // comparison, so that we have individual loads to + // replace. + newval := renamed(u.Parent(), renaming, alloc) + if debugLifting { + fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", + instr.Name(), instr, newval) + } + replaceAll(instr, newval) + u.Instrs[i] = nil + u.gaps++ + } + + case *DebugRef: + if x, ok := instr.X.(*Alloc); ok && x.index >= 0 { + if instr.IsAddr { + instr.X = renamed(u.Parent(), renaming, x) + instr.IsAddr = false + + // Add DebugRef to instr.X's referrers. + if refs := instr.X.Referrers(); refs != nil { + *refs = append(*refs, instr) + } + } else { + // A source expression denotes the address + // of an Alloc that was optimized away. + instr.X = nil + + // Delete the DebugRef. + u.Instrs[i] = nil + u.gaps++ + } + } + } + } + + // update all outgoing sigma nodes with the dominating store + for _, sigmas := range newSigmas[u.Index] { + for _, sigma := range sigmas.sigmas { + if sigma == nil { + continue + } + sigma.X = renamed(u.Parent(), renaming, sigmas.alloc) + } + } + + // For each φ-node in a CFG successor, rename the edge. + for succi, v := range u.Succs { + phis := newPhis[v.Index] + if len(phis) == 0 { + continue + } + i := v.predIndex(u) + for _, np := range phis { + phi := np.phi + alloc := np.alloc + // if there's a sigma node, use it, else use the dominating value + var newval Value + for _, sigmas := range newSigmas[u.Index] { + if sigmas.alloc == alloc && sigmas.sigmas[succi] != nil { + newval = sigmas.sigmas[succi] + break + } + } + if newval == nil { + newval = renamed(u.Parent(), renaming, alloc) + } + if debugLifting { + fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", + phi.Name(), u, v, i, alloc.Name(), newval.Name()) + } + phi.Edges[i] = newval + if prefs := newval.Referrers(); prefs != nil { + *prefs = append(*prefs, phi) + } + } + } + + // Continue depth-first recursion over domtree, pushing a + // fresh copy of the renaming map for each subtree. + r := make([]Value, len(renaming)) + for _, v := range u.dom.children { + // XXX add debugging + copy(r, renaming) + + // on entry to a block, the incoming sigma nodes become the new values for their alloc + if idx := u.succIndex(v); idx != -1 { + for _, sigma := range newSigmas[u.Index] { + if sigma.sigmas[idx] != nil { + r[sigma.alloc.index] = sigma.sigmas[idx] + } + } + } + rename(v, r, newPhis, newSigmas) + } + +} diff --git a/vendor/honnef.co/go/tools/ssa/lvalue.go b/vendor/honnef.co/go/tools/ir/lvalue.go similarity index 59% rename from vendor/honnef.co/go/tools/ssa/lvalue.go rename to vendor/honnef.co/go/tools/ir/lvalue.go index eb5d71e188..f676a1f7ab 100644 --- a/vendor/honnef.co/go/tools/ssa/lvalue.go +++ b/vendor/honnef.co/go/tools/ir/lvalue.go @@ -2,14 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // lvalues are the union of addressable expressions and map-index // expressions. import ( "go/ast" - "go/token" "go/types" ) @@ -18,27 +17,24 @@ import ( // pointer to permit updates to elements of maps. // type lvalue interface { - store(fn *Function, v Value) // stores v into the location - load(fn *Function) Value // loads the contents of the location - address(fn *Function) Value // address of the location - typ() types.Type // returns the type of the location + store(fn *Function, v Value, source ast.Node) // stores v into the location + load(fn *Function, source ast.Node) Value // loads the contents of the location + address(fn *Function) Value // address of the location + typ() types.Type // returns the type of the location } // An address is an lvalue represented by a true pointer. type address struct { addr Value - pos token.Pos // source position - expr ast.Expr // source syntax of the value (not address) [debug mode] + expr ast.Expr // source syntax of the value (not address) [debug mode] } -func (a *address) load(fn *Function) Value { - load := emitLoad(fn, a.addr) - load.pos = a.pos - return load +func (a *address) load(fn *Function, source ast.Node) Value { + return emitLoad(fn, a.addr, source) } -func (a *address) store(fn *Function, v Value) { - store := emitStore(fn, a.addr, v, a.pos) +func (a *address) store(fn *Function, v Value, source ast.Node) { + store := emitStore(fn, a.addr, v, source) if a.expr != nil { // store.Val is v, converted for assignability. emitDebugRef(fn, a.expr, store.Val, false) @@ -57,38 +53,35 @@ func (a *address) typ() types.Type { } // An element is an lvalue represented by m[k], the location of an -// element of a map or string. These locations are not addressable +// element of a map. These locations are not addressable // since pointers cannot be formed from them, but they do support -// load(), and in the case of maps, store(). +// load() and store(). // type element struct { - m, k Value // map or string - t types.Type // map element type or string byte type - pos token.Pos // source position of colon ({k:v}) or lbrack (m[k]=v) + m, k Value // map + t types.Type // map element type } -func (e *element) load(fn *Function) Value { - l := &Lookup{ +func (e *element) load(fn *Function, source ast.Node) Value { + l := &MapLookup{ X: e.m, Index: e.k, } - l.setPos(e.pos) l.setType(e.t) - return fn.emit(l) + return fn.emit(l, source) } -func (e *element) store(fn *Function, v Value) { +func (e *element) store(fn *Function, v Value, source ast.Node) { up := &MapUpdate{ Map: e.m, Key: e.k, - Value: emitConv(fn, v, e.t), + Value: emitConv(fn, v, e.t, source), } - up.pos = e.pos - fn.emit(up) + fn.emit(up, source) } func (e *element) address(fn *Function) Value { - panic("map/string elements are not addressable") + panic("map elements are not addressable") } func (e *element) typ() types.Type { @@ -100,15 +93,15 @@ func (e *element) typ() types.Type { // type blank struct{} -func (bl blank) load(fn *Function) Value { +func (bl blank) load(fn *Function, source ast.Node) Value { panic("blank.load is illegal") } -func (bl blank) store(fn *Function, v Value) { +func (bl blank) store(fn *Function, v Value, source ast.Node) { s := &BlankStore{ Val: v, } - fn.emit(s) + fn.emit(s, source) } func (bl blank) address(fn *Function) Value { diff --git a/vendor/honnef.co/go/tools/ssa/methods.go b/vendor/honnef.co/go/tools/ir/methods.go similarity index 99% rename from vendor/honnef.co/go/tools/ssa/methods.go rename to vendor/honnef.co/go/tools/ir/methods.go index 9cf383916b..517f448b8c 100644 --- a/vendor/honnef.co/go/tools/ssa/methods.go +++ b/vendor/honnef.co/go/tools/ir/methods.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines utilities for population of method sets. diff --git a/vendor/honnef.co/go/tools/ssa/mode.go b/vendor/honnef.co/go/tools/ir/mode.go similarity index 65% rename from vendor/honnef.co/go/tools/ssa/mode.go rename to vendor/honnef.co/go/tools/ir/mode.go index d2a269893a..da548fdbb2 100644 --- a/vendor/honnef.co/go/tools/ssa/mode.go +++ b/vendor/honnef.co/go/tools/ir/mode.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines the BuilderMode type and its command-line flag. @@ -15,32 +15,30 @@ import ( // // *BuilderMode satisfies the flag.Value interface. Example: // -// var mode = ssa.BuilderMode(0) -// func init() { flag.Var(&mode, "build", ssa.BuilderModeDoc) } +// var mode = ir.BuilderMode(0) +// func init() { flag.Var(&mode, "build", ir.BuilderModeDoc) } // type BuilderMode uint const ( PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout - PrintFunctions // Print function SSA code to stdout - LogSource // Log source locations as SSA builder progresses + PrintFunctions // Print function IR code to stdout + PrintSource // Print source code when printing function IR + LogSource // Log source locations as IR builder progresses SanityCheckFunctions // Perform sanity checking of function bodies - NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers - BuildSerially // Build packages serially, not in parallel. + NaiveForm // Build naïve IR form: don't replace local loads/stores with registers GlobalDebug // Enable debug info for all packages - BareInits // Build init functions without guards or calls to dependent inits ) -const BuilderModeDoc = `Options controlling the SSA builder. +const BuilderModeDoc = `Options controlling the IR builder. The value is a sequence of zero or more of these letters: -C perform sanity [C]hecking of the SSA form. +C perform sanity [C]hecking of the IR form. D include [D]ebug info for every function. P print [P]ackage inventory. -F print [F]unction SSA code. -S log [S]ource locations as SSA builder progresses. -L build distinct packages seria[L]ly instead of in parallel. -N build [N]aive SSA form: don't replace local loads/stores with registers. -I build bare [I]nit functions: no init guards or calls to dependent inits. +F print [F]unction IR code. +A print [A]ST nodes responsible for IR instructions +S log [S]ource locations as IR builder progresses. +N build [N]aive IR form: don't replace local loads/stores with registers. ` func (m BuilderMode) String() string { @@ -54,6 +52,9 @@ func (m BuilderMode) String() string { if m&PrintFunctions != 0 { buf.WriteByte('F') } + if m&PrintSource != 0 { + buf.WriteByte('A') + } if m&LogSource != 0 { buf.WriteByte('S') } @@ -63,9 +64,6 @@ func (m BuilderMode) String() string { if m&NaiveForm != 0 { buf.WriteByte('N') } - if m&BuildSerially != 0 { - buf.WriteByte('L') - } return buf.String() } @@ -80,14 +78,14 @@ func (m *BuilderMode) Set(s string) error { mode |= PrintPackages case 'F': mode |= PrintFunctions + case 'A': + mode |= PrintSource case 'S': - mode |= LogSource | BuildSerially + mode |= LogSource case 'C': mode |= SanityCheckFunctions case 'N': mode |= NaiveForm - case 'L': - mode |= BuildSerially default: return fmt.Errorf("unknown BuilderMode option: %q", c) } diff --git a/vendor/honnef.co/go/tools/ssa/print.go b/vendor/honnef.co/go/tools/ir/print.go similarity index 54% rename from vendor/honnef.co/go/tools/ssa/print.go rename to vendor/honnef.co/go/tools/ir/print.go index 6fd277277c..c16c08efa6 100644 --- a/vendor/honnef.co/go/tools/ssa/print.go +++ b/vendor/honnef.co/go/tools/ir/print.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file implements the String() methods for all Value and // Instruction types. @@ -25,6 +25,9 @@ import ( // references are package-qualified. // func relName(v Value, i Instruction) string { + if v == nil { + return "" + } var from *types.Package if i != nil { from = i.Parent().pkg() @@ -32,8 +35,6 @@ func relName(v Value, i Instruction) string { switch v := v.(type) { case Member: // *Function or *Global return v.RelString(from) - case *Const: - return v.RelString(from) } return v.Name() } @@ -58,36 +59,40 @@ func relString(m Member, from *types.Package) string { func (v *Parameter) String() string { from := v.Parent().pkg() - return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from)) + return fmt.Sprintf("Parameter <%s> {%s}", relType(v.Type(), from), v.name) } func (v *FreeVar) String() string { from := v.Parent().pkg() - return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from)) + return fmt.Sprintf("FreeVar <%s> %s", relType(v.Type(), from), v.Name()) } func (v *Builtin) String() string { - return fmt.Sprintf("builtin %s", v.Name()) + return fmt.Sprintf("Builtin %s", v.Name()) } // Instruction.String() func (v *Alloc) String() string { - op := "local" - if v.Heap { - op = "new" - } from := v.Parent().pkg() - return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment) + storage := "Stack" + if v.Heap { + storage = "Heap" + } + return fmt.Sprintf("%sAlloc <%s>", storage, relType(v.Type(), from)) +} + +func (v *Sigma) String() string { + from := v.Parent().pkg() + s := fmt.Sprintf("Sigma <%s> [b%d] %s", relType(v.Type(), from), v.From.Index, v.X.Name()) + return s } func (v *Phi) String() string { var b bytes.Buffer - b.WriteString("phi [") + fmt.Fprintf(&b, "Phi <%s>", v.Type()) for i, edge := range v.Edges { - if i > 0 { - b.WriteString(", ") - } + b.WriteString(" ") // Be robust against malformed CFG. if v.block == nil { b.WriteString("??") @@ -97,40 +102,35 @@ func (v *Phi) String() string { if i < len(v.block.Preds) { block = v.block.Preds[i].Index } - fmt.Fprintf(&b, "%d: ", block) + fmt.Fprintf(&b, "%d:", block) edgeVal := "" // be robust if edge != nil { edgeVal = relName(edge, v) } b.WriteString(edgeVal) } - b.WriteString("]") - if v.Comment != "" { - b.WriteString(" #") - b.WriteString(v.Comment) - } return b.String() } func printCall(v *CallCommon, prefix string, instr Instruction) string { var b bytes.Buffer - b.WriteString(prefix) if !v.IsInvoke() { - b.WriteString(relName(v.Value, instr)) - } else { - fmt.Fprintf(&b, "invoke %s.%s", relName(v.Value, instr), v.Method.Name()) - } - b.WriteString("(") - for i, arg := range v.Args { - if i > 0 { - b.WriteString(", ") + if value, ok := instr.(Value); ok { + fmt.Fprintf(&b, "%s <%s> %s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr)) + } else { + fmt.Fprintf(&b, "%s %s", prefix, relName(v.Value, instr)) } + } else { + if value, ok := instr.(Value); ok { + fmt.Fprintf(&b, "%sInvoke <%s> %s.%s", prefix, relType(value.Type(), instr.Parent().pkg()), relName(v.Value, instr), v.Method.Name()) + } else { + fmt.Fprintf(&b, "%sInvoke %s.%s", prefix, relName(v.Value, instr), v.Method.Name()) + } + } + for _, arg := range v.Args { + b.WriteString(" ") b.WriteString(relName(arg, instr)) } - if v.Signature().Variadic() { - b.WriteString("...") - } - b.WriteString(")") return b.String() } @@ -139,73 +139,59 @@ func (c *CallCommon) String() string { } func (v *Call) String() string { - return printCall(&v.Call, "", v) + return printCall(&v.Call, "Call", v) } func (v *BinOp) String() string { - return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v)) + return fmt.Sprintf("BinOp <%s> {%s} %s %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v), relName(v.Y, v)) } func (v *UnOp) String() string { - return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk)) + return fmt.Sprintf("UnOp <%s> {%s} %s", relType(v.Type(), v.Parent().pkg()), v.Op.String(), relName(v.X, v)) +} + +func (v *Load) String() string { + return fmt.Sprintf("Load <%s> %s", relType(v.Type(), v.Parent().pkg()), relName(v.X, v)) } func printConv(prefix string, v, x Value) string { from := v.Parent().pkg() - return fmt.Sprintf("%s %s <- %s (%s)", + return fmt.Sprintf("%s <%s> %s", prefix, relType(v.Type(), from), - relType(x.Type(), from), relName(x, v.(Instruction))) } -func (v *ChangeType) String() string { return printConv("changetype", v, v.X) } -func (v *Convert) String() string { return printConv("convert", v, v.X) } -func (v *ChangeInterface) String() string { return printConv("change interface", v, v.X) } -func (v *MakeInterface) String() string { return printConv("make", v, v.X) } +func (v *ChangeType) String() string { return printConv("ChangeType", v, v.X) } +func (v *Convert) String() string { return printConv("Convert", v, v.X) } +func (v *ChangeInterface) String() string { return printConv("ChangeInterface", v, v.X) } +func (v *MakeInterface) String() string { return printConv("MakeInterface", v, v.X) } func (v *MakeClosure) String() string { + from := v.Parent().pkg() var b bytes.Buffer - fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v)) + fmt.Fprintf(&b, "MakeClosure <%s> %s", relType(v.Type(), from), relName(v.Fn, v)) if v.Bindings != nil { - b.WriteString(" [") - for i, c := range v.Bindings { - if i > 0 { - b.WriteString(", ") - } + for _, c := range v.Bindings { + b.WriteString(" ") b.WriteString(relName(c, v)) } - b.WriteString("]") } return b.String() } func (v *MakeSlice) String() string { from := v.Parent().pkg() - return fmt.Sprintf("make %s %s %s", + return fmt.Sprintf("MakeSlice <%s> %s %s", relType(v.Type(), from), relName(v.Len, v), relName(v.Cap, v)) } func (v *Slice) String() string { - var b bytes.Buffer - b.WriteString("slice ") - b.WriteString(relName(v.X, v)) - b.WriteString("[") - if v.Low != nil { - b.WriteString(relName(v.Low, v)) - } - b.WriteString(":") - if v.High != nil { - b.WriteString(relName(v.High, v)) - } - if v.Max != nil { - b.WriteString(":") - b.WriteString(relName(v.Max, v)) - } - b.WriteString("]") - return b.String() + from := v.Parent().pkg() + return fmt.Sprintf("Slice <%s> %s %s %s %s", + relType(v.Type(), from), relName(v.X, v), relName(v.Low, v), relName(v.High, v), relName(v.Max, v)) } func (v *MakeMap) String() string { @@ -214,22 +200,23 @@ func (v *MakeMap) String() string { res = relName(v.Reserve, v) } from := v.Parent().pkg() - return fmt.Sprintf("make %s %s", relType(v.Type(), from), res) + return fmt.Sprintf("MakeMap <%s> %s", relType(v.Type(), from), res) } func (v *MakeChan) String() string { from := v.Parent().pkg() - return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v)) + return fmt.Sprintf("MakeChan <%s> %s", relType(v.Type(), from), relName(v.Size, v)) } func (v *FieldAddr) String() string { + from := v.Parent().pkg() st := deref(v.X.Type()).Underlying().(*types.Struct) // Be robust against a bad index. name := "?" if 0 <= v.Field && v.Field < st.NumFields() { name = st.Field(v.Field).Name() } - return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field) + return fmt.Sprintf("FieldAddr <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v)) } func (v *Field) String() string { @@ -239,36 +226,49 @@ func (v *Field) String() string { if 0 <= v.Field && v.Field < st.NumFields() { name = st.Field(v.Field).Name() } - return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field) + from := v.Parent().pkg() + return fmt.Sprintf("Field <%s> [%d] (%s) %s", relType(v.Type(), from), v.Field, name, relName(v.X, v)) } func (v *IndexAddr) String() string { - return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v)) + from := v.Parent().pkg() + return fmt.Sprintf("IndexAddr <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } func (v *Index) String() string { - return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v)) + from := v.Parent().pkg() + return fmt.Sprintf("Index <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } -func (v *Lookup) String() string { - return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk)) +func (v *MapLookup) String() string { + from := v.Parent().pkg() + return fmt.Sprintf("MapLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) +} + +func (v *StringLookup) String() string { + from := v.Parent().pkg() + return fmt.Sprintf("StringLookup <%s> %s %s", relType(v.Type(), from), relName(v.X, v), relName(v.Index, v)) } func (v *Range) String() string { - return "range " + relName(v.X, v) + from := v.Parent().pkg() + return fmt.Sprintf("Range <%s> %s", relType(v.Type(), from), relName(v.X, v)) } func (v *Next) String() string { - return "next " + relName(v.Iter, v) + from := v.Parent().pkg() + return fmt.Sprintf("Next <%s> %s", relType(v.Type(), from), relName(v.Iter, v)) } func (v *TypeAssert) String() string { from := v.Parent().pkg() - return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from)) + return fmt.Sprintf("TypeAssert <%s> %s", relType(v.Type(), from), relName(v.X, v)) } func (v *Extract) String() string { - return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index) + from := v.Parent().pkg() + name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() + return fmt.Sprintf("Extract <%s> [%d] (%s) %s", relType(v.Type(), from), v.Index, name, relName(v.Tuple, v)) } func (s *Jump) String() string { @@ -277,7 +277,20 @@ func (s *Jump) String() string { if s.block != nil && len(s.block.Succs) == 1 { block = s.block.Succs[0].Index } - return fmt.Sprintf("jump %d", block) + str := fmt.Sprintf("Jump → b%d", block) + if s.Comment != "" { + str = fmt.Sprintf("%s # %s", str, s.Comment) + } + return str +} + +func (s *Unreachable) String() string { + // Be robust against malformed CFG. + block := -1 + if s.block != nil && len(s.block.Succs) == 1 { + block = s.block.Succs[0].Index + } + return fmt.Sprintf("Unreachable → b%d", block) } func (s *If) String() string { @@ -287,41 +300,70 @@ func (s *If) String() string { tblock = s.block.Succs[0].Index fblock = s.block.Succs[1].Index } - return fmt.Sprintf("if %s goto %d else %d", relName(s.Cond, s), tblock, fblock) + return fmt.Sprintf("If %s → b%d b%d", relName(s.Cond, s), tblock, fblock) +} + +func (s *ConstantSwitch) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "ConstantSwitch %s", relName(s.Tag, s)) + for _, cond := range s.Conds { + fmt.Fprintf(&b, " %s", relName(cond, s)) + } + fmt.Fprint(&b, " →") + for _, succ := range s.block.Succs { + fmt.Fprintf(&b, " b%d", succ.Index) + } + return b.String() +} + +func (s *TypeSwitch) String() string { + from := s.Parent().pkg() + var b bytes.Buffer + fmt.Fprintf(&b, "TypeSwitch <%s> %s", relType(s.typ, from), relName(s.Tag, s)) + for _, cond := range s.Conds { + fmt.Fprintf(&b, " %q", relType(cond, s.block.parent.pkg())) + } + return b.String() } func (s *Go) String() string { - return printCall(&s.Call, "go ", s) + return printCall(&s.Call, "Go", s) } func (s *Panic) String() string { - return "panic " + relName(s.X, s) + // Be robust against malformed CFG. + block := -1 + if s.block != nil && len(s.block.Succs) == 1 { + block = s.block.Succs[0].Index + } + return fmt.Sprintf("Panic %s → b%d", relName(s.X, s), block) } func (s *Return) String() string { var b bytes.Buffer - b.WriteString("return") - for i, r := range s.Results { - if i == 0 { - b.WriteString(" ") - } else { - b.WriteString(", ") - } + b.WriteString("Return") + for _, r := range s.Results { + b.WriteString(" ") b.WriteString(relName(r, s)) } return b.String() } func (*RunDefers) String() string { - return "rundefers" + return "RunDefers" } func (s *Send) String() string { - return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s)) + return fmt.Sprintf("Send %s %s", relName(s.Chan, s), relName(s.X, s)) +} + +func (recv *Recv) String() string { + from := recv.Parent().pkg() + return fmt.Sprintf("Recv <%s> %s", relType(recv.Type(), from), relName(recv.Chan, recv)) } func (s *Defer) String() string { - return printCall(&s.Call, "defer ", s) + return printCall(&s.Call, "Defer", s) } func (s *Select) String() string { @@ -341,21 +383,23 @@ func (s *Select) String() string { } non := "" if !s.Blocking { - non = "non" + non = "Non" } - return fmt.Sprintf("select %sblocking [%s]", non, b.String()) + from := s.Parent().pkg() + return fmt.Sprintf("Select%sBlocking <%s> [%s]", non, relType(s.Type(), from), b.String()) } func (s *Store) String() string { - return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s)) + return fmt.Sprintf("Store {%s} %s %s", + s.Val.Type(), relName(s.Addr, s), relName(s.Val, s)) } func (s *BlankStore) String() string { - return fmt.Sprintf("_ = %s", relName(s.Val, s)) + return fmt.Sprintf("BlankStore %s", relName(s.Val, s)) } func (s *MapUpdate) String() string { - return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s)) + return fmt.Sprintf("MapUpdate %s %s %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s)) } func (s *DebugRef) String() string { @@ -426,10 +470,3 @@ func WritePackage(buf *bytes.Buffer, p *Package) { fmt.Fprintf(buf, "\n") } - -func commaOk(x bool) string { - if x { - return ",ok" - } - return "" -} diff --git a/vendor/honnef.co/go/tools/ssa/sanity.go b/vendor/honnef.co/go/tools/ir/sanity.go similarity index 89% rename from vendor/honnef.co/go/tools/ssa/sanity.go rename to vendor/honnef.co/go/tools/ir/sanity.go index 1d29b66b02..ff9edbc646 100644 --- a/vendor/honnef.co/go/tools/ssa/sanity.go +++ b/vendor/honnef.co/go/tools/ir/sanity.go @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir -// An optional pass for sanity-checking invariants of the SSA representation. +// An optional pass for sanity-checking invariants of the IR representation. // Currently it checks CFG invariants but little at the instruction level. import ( @@ -23,7 +23,7 @@ type sanity struct { insane bool } -// sanityCheck performs integrity checking of the SSA representation +// sanityCheck performs integrity checking of the IR representation // of the function fn and returns true if it was valid. Diagnostics // are written to reporter if non-nil, os.Stderr otherwise. Some // diagnostics are only warnings and do not imply a negative result. @@ -89,8 +89,15 @@ func findDuplicate(blocks []*BasicBlock) *BasicBlock { func (s *sanity) checkInstr(idx int, instr Instruction) { switch instr := instr.(type) { - case *If, *Jump, *Return, *Panic: + case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch: s.errorf("control flow instruction not at end of block") + case *Sigma: + if idx > 0 { + prev := s.block.Instrs[idx-1] + if _, ok := prev.(*Sigma); !ok { + s.errorf("Sigma instruction follows a non-Sigma: %T", prev) + } + } case *Phi: if idx == 0 { // It suffices to apply this check to just the first phi node. @@ -99,8 +106,10 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { } } else { prev := s.block.Instrs[idx-1] - if _, ok := prev.(*Phi); !ok { - s.errorf("Phi instruction follows a non-Phi: %T", prev) + switch prev.(type) { + case *Phi, *Sigma: + default: + s.errorf("Phi instruction follows a non-Phi, non-Sigma: %T", prev) } } if ne, np := len(instr.Edges), len(s.block.Preds); ne != np { @@ -109,7 +118,7 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { } else { for i, e := range instr.Edges { if e == nil { - s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment, i, s.block.Preds[i]) + s.errorf("phi node '%v' has no value for edge #%d from %s", instr, i, s.block.Preds[i]) } } } @@ -146,7 +155,8 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *Go: case *Index: case *IndexAddr: - case *Lookup: + case *MapLookup: + case *StringLookup: case *MakeChan: case *MakeClosure: numFree := len(instr.Fn.(*Function).FreeVars) @@ -175,8 +185,11 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *UnOp: case *DebugRef: case *BlankStore: - case *Sigma: - // TODO(adonovan): implement checks. + case *Load: + case *Parameter: + case *Const: + case *Recv: + case *TypeSwitch: default: panic(fmt.Sprintf("Unknown instruction type: %T", instr)) } @@ -196,7 +209,9 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { } else if t == tRangeIter { // not a proper type; ignore. } else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { - s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t) + if _, ok := v.(*Const); !ok { + s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t) + } } s.checkReferrerList(v) } @@ -239,11 +254,19 @@ func (s *sanity) checkFinalInstr(instr Instruction) { } case *Panic: - if nsuccs := len(s.block.Succs); nsuccs != 0 { - s.errorf("Panic-terminated block has %d successors; expected none", nsuccs) + if nsuccs := len(s.block.Succs); nsuccs != 1 { + s.errorf("Panic-terminated block has %d successors; expected one", nsuccs) return } + case *Unreachable: + if nsuccs := len(s.block.Succs); nsuccs != 1 { + s.errorf("Unreachable-terminated block has %d successors; expected one", nsuccs) + return + } + + case *ConstantSwitch: + default: s.errorf("non-control flow instruction at end of block") } @@ -260,9 +283,8 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) { } // Check all blocks are reachable. - // (The entry block is always implicitly reachable, - // as is the Recover block, if any.) - if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 { + // (The entry block is always implicitly reachable, the exit block may be unreachable.) + if index > 1 && len(b.Preds) == 0 { s.warnf("unreachable block") if b.Instrs == nil { // Since this block is about to be pruned, @@ -395,7 +417,11 @@ func (s *sanity) checkReferrerList(v Value) { } for i, ref := range *refs { if _, ok := s.instrs[ref]; !ok { - s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref) + if val, ok := ref.(Value); ok { + s.errorf("%s.Referrers()[%d] = %s = %s is not an instruction belonging to this function", v.Name(), i, val.Name(), val) + } else { + s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref) + } } } } @@ -426,7 +452,7 @@ func (s *sanity) checkFunction(fn *Function) bool { s.errorf("nil Pkg") } } - if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn { + if src, syn := fn.Synthetic == "", fn.source != nil; src != syn { s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn) } for i, l := range fn.Locals { @@ -481,9 +507,6 @@ func (s *sanity) checkFunction(fn *Function) bool { } s.checkBlock(b, i) } - if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover { - s.errorf("Recover block is not in Blocks slice") - } s.block = nil for i, anon := range fn.AnonFuncs { @@ -522,14 +545,11 @@ func sanityCheckPackage(pkg *Package) { if obj.Name() != name { if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") { // Ok. The name of a declared init function varies between - // its types.Func ("init") and its ssa.Function ("init#%d"). + // its types.Func ("init") and its ir.Function ("init#%d"). } else { panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s", pkg.Pkg.Path(), mem, obj.Name(), name)) } } - if obj.Pos() != mem.Pos() { - panic(fmt.Sprintf("%s Pos=%d obj.Pos=%d", mem, mem.Pos(), obj.Pos())) - } } } diff --git a/vendor/honnef.co/go/tools/ssa/source.go b/vendor/honnef.co/go/tools/ir/source.go similarity index 80% rename from vendor/honnef.co/go/tools/ssa/source.go rename to vendor/honnef.co/go/tools/ir/source.go index 8d9cca1703..93d1ccbd29 100644 --- a/vendor/honnef.co/go/tools/ssa/source.go +++ b/vendor/honnef.co/go/tools/ir/source.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines utilities for working with source positions // or source-level named entities ("objects"). @@ -25,7 +25,7 @@ import ( // Returns nil if not found; reasons might include: // - the node is not enclosed by any function. // - the node is within an anonymous function (FuncLit) and -// its SSA function has not been created yet +// its IR function has not been created yet // (pkg.Build() has not yet been called). // func EnclosingFunction(pkg *Package, path []ast.Node) *Function { @@ -46,7 +46,7 @@ outer: continue outer } } - // SSA function not found: + // IR function not found: // - package not yet built, or maybe // - builder skipped FuncLit in dead block // (in principle; but currently the Builder @@ -62,9 +62,9 @@ outer: // package-level variable. // // Unlike EnclosingFunction, the behaviour of this function does not -// depend on whether SSA code for pkg has been built, so it can be +// depend on whether IR code for pkg has been built, so it can be // used to quickly reject check inputs that will cause -// EnclosingFunction to fail, prior to SSA building. +// EnclosingFunction to fail, prior to IR building. // func HasEnclosingFunction(pkg *Package, path []ast.Node) bool { return findEnclosingPackageLevelFunction(pkg, path) != nil @@ -83,23 +83,14 @@ func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function } case *ast.FuncDecl: - if decl.Recv == nil && decl.Name.Name == "init" { - // Explicit init() function. - for _, b := range pkg.init.Blocks { - for _, instr := range b.Instrs { - if instr, ok := instr.(*Call); ok { - if callee, ok := instr.Call.Value.(*Function); ok && callee.Pkg == pkg && callee.Pos() == decl.Name.NamePos { - return callee - } - } - } - } - // Hack: return non-nil when SSA is not yet + // Declared function/method. + fn := findNamedFunc(pkg, decl.Pos()) + if fn == nil && decl.Recv == nil && decl.Name.Name == "init" { + // Hack: return non-nil when IR is not yet // built so that HasEnclosingFunction works. return pkg.init } - // Declared function/method. - return findNamedFunc(pkg, decl.Name.NamePos) + return fn } } return nil // not in any function @@ -109,29 +100,15 @@ func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function // position pos. // func findNamedFunc(pkg *Package, pos token.Pos) *Function { - // Look at all package members and method sets of named types. - // Not very efficient. - for _, mem := range pkg.Members { - switch mem := mem.(type) { - case *Function: - if mem.Pos() == pos { - return mem - } - case *Type: - mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) - for i, n := 0, mset.Len(); i < n; i++ { - // Don't call Program.Method: avoid creating wrappers. - obj := mset.At(i).Obj().(*types.Func) - if obj.Pos() == pos { - return pkg.values[obj].(*Function) - } - } + for _, fn := range pkg.Functions { + if fn.Pos() == pos { + return fn } } return nil } -// ValueForExpr returns the SSA Value that corresponds to non-constant +// ValueForExpr returns the IR Value that corresponds to non-constant // expression e. // // It returns nil if no value was found, e.g. @@ -149,10 +126,10 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function { // The types of e (or &e, if isAddr) and the result are equal // (modulo "untyped" bools resulting from comparisons). // -// (Tip: to find the ssa.Value given a source position, use +// (Tip: to find the ir.Value given a source position, use // astutil.PathEnclosingInterval to locate the ast.Node, then // EnclosingFunction to locate the Function, then ValueForExpr to find -// the ssa.Value.) +// the ir.Value.) // func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { if f.debugInfo() { // (opt) @@ -172,9 +149,9 @@ func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { // --- Lookup functions for source-level named entities (types.Objects) --- -// Package returns the SSA Package corresponding to the specified +// Package returns the IR Package corresponding to the specified // type-checker package object. -// It returns nil if no such SSA package has been created. +// It returns nil if no such IR package has been created. // func (prog *Program) Package(obj *types.Package) *Package { return prog.packages[obj] @@ -203,7 +180,7 @@ func (prog *Program) FuncValue(obj *types.Func) *Function { return fn } -// ConstValue returns the SSA Value denoted by the source-level named +// ConstValue returns the IR Value denoted by the source-level named // constant obj. // func (prog *Program) ConstValue(obj *types.Const) *Const { @@ -221,12 +198,12 @@ func (prog *Program) ConstValue(obj *types.Const) *Const { return NewConst(obj.Val(), obj.Type()) } -// VarValue returns the SSA Value that corresponds to a specific +// VarValue returns the IR Value that corresponds to a specific // identifier denoting the source-level named variable obj. // // VarValue returns nil if a local variable was not found, perhaps // because its package was not built, the debug information was not -// requested during SSA construction, or the value was optimized away. +// requested during IR construction, or the value was optimized away. // // ref is the path to an ast.Ident (e.g. from PathEnclosingInterval), // and that ident must resolve to obj. @@ -252,14 +229,14 @@ func (prog *Program) ConstValue(obj *types.Const) *Const { // // It is not specified whether the value or the address is returned in // any particular case, as it may depend upon optimizations performed -// during SSA code generation, such as registerization, constant +// during IR code generation, such as registerization, constant // folding, avoidance of materialization of subexpressions, etc. // func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) { // All references to a var are local to some function, possibly init. fn := EnclosingFunction(pkg, ref) if fn == nil { - return // e.g. def of struct field; SSA not built? + return // e.g. def of struct field; IR not built? } id := ref[0].(*ast.Ident) diff --git a/vendor/honnef.co/go/tools/ssa/ssa.go b/vendor/honnef.co/go/tools/ir/ssa.go similarity index 78% rename from vendor/honnef.co/go/tools/ssa/ssa.go rename to vendor/honnef.co/go/tools/ir/ssa.go index aeddd65e58..49693045f0 100644 --- a/vendor/honnef.co/go/tools/ssa/ssa.go +++ b/vendor/honnef.co/go/tools/ir/ssa.go @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This package defines a high-level intermediate representation for -// Go programs using static single-assignment (SSA) form. +// Go programs using static single-information (SSI) form. import ( "fmt" @@ -18,12 +18,15 @@ import ( "golang.org/x/tools/go/types/typeutil" ) -// A Program is a partial or complete Go program converted to SSA form. +type ID int + +// A Program is a partial or complete Go program converted to IR form. type Program struct { Fset *token.FileSet // position information for the files of this Program + PrintFunc string // create ir.html for function specified in PrintFunc imported map[string]*Package // all importable Packages, keyed by import path packages map[*types.Package]*Package // all loaded Packages, keyed by object - mode BuilderMode // set of mode bits for SSA construction + mode BuilderMode // set of mode bits for IR construction MethodSets typeutil.MethodSetCache // cache of type-checker's method-sets methodsMu sync.Mutex // guards the following maps: @@ -44,12 +47,14 @@ type Program struct { // and unspecified other things too. // type Package struct { - Prog *Program // the owning program - Pkg *types.Package // the corresponding go/types.Package - Members map[string]Member // all package members keyed by name (incl. init and init#%d) - values map[types.Object]Value // package members (incl. types and methods), keyed by object - init *Function // Func("init"); the package's init function - debug bool // include full debug info in this package + Prog *Program // the owning program + Pkg *types.Package // the corresponding go/types.Package + Members map[string]Member // all package members keyed by name (incl. init and init#%d) + Functions []*Function // all functions, excluding anonymous ones + values map[types.Object]Value // package members (incl. types and methods), keyed by object + init *Function // Func("init"); the package's init function + debug bool // include full debug info in this package + printFunc string // which function to print in HTML form // The following fields are set transiently, then cleared // after building. @@ -68,7 +73,6 @@ type Member interface { String() string // package-qualified name of the package member RelString(*types.Package) string // like String, but relative refs are unqualified Object() types.Object // typechecker's object for this member, if any - Pos() token.Pos // position of member's declaration, if known Type() types.Type // type of the package member Token() token.Token // token.{VAR,FUNC,CONST,TYPE} Package() *Package // the containing package @@ -95,8 +99,10 @@ type NamedConst struct { pkg *Package } -// A Value is an SSA value that can be referenced by an instruction. +// A Value is an IR value that can be referenced by an instruction. type Value interface { + setID(ID) + // Name returns the name of this value, and determines how // this Value appears when used as an operand of an // Instruction. @@ -107,10 +113,21 @@ type Value interface { // and type. For all other Values this is the name of the // virtual register defined by the instruction. // - // The name of an SSA Value is not semantically significant, + // The name of an IR Value is not semantically significant, // and may not even be unique within a function. Name() string + // ID returns the ID of this value. IDs are unique within a single + // function and are densely numbered, but may contain gaps. + // Values and other Instructions share the same ID space. + // Globally, values are identified by their addresses. However, + // IDs exist to facilitate efficient storage of mappings between + // values and data when analysing functions. + // + // NB: IDs are allocated late in the IR construction process and + // are not available to early stages of said process. + ID() ID + // If this value is an Instruction, String returns its // disassembled form; otherwise it returns unspecified // human-readable information about the Value, such as its @@ -123,7 +140,7 @@ type Value interface { Type() types.Type // Parent returns the function to which this Value belongs. - // It returns nil for named Functions, Builtin, Const and Global. + // It returns nil for named Functions, Builtin and Global. Parent() *Function // Referrers returns the list of instructions that have this @@ -136,29 +153,27 @@ type Value interface { // Referrers is currently only defined if Parent()!=nil, // i.e. for the function-local values FreeVar, Parameter, // Functions (iff anonymous) and all value-defining instructions. - // It returns nil for named Functions, Builtin, Const and Global. + // It returns nil for named Functions, Builtin and Global. // // Instruction.Operands contains the inverse of this relation. Referrers() *[]Instruction - // Pos returns the location of the AST token most closely - // associated with the operation that gave rise to this value, - // or token.NoPos if it was not explicit in the source. + Operands(rands []*Value) []*Value // nil for non-Instructions + + // Source returns the AST node responsible for creating this + // value. A single AST node may be responsible for more than one + // value, and not all values have an associated AST node. // - // For each ast.Node type, a particular token is designated as - // the closest location for the expression, e.g. the Lparen - // for an *ast.CallExpr. This permits a compact but - // approximate mapping from Values to source positions for use - // in diagnostic messages, for example. - // - // (Do not use this position to determine which Value - // corresponds to an ast.Expr; use Function.ValueForExpr - // instead. NB: it requires that the function was built with - // debug information.) + // Do not use this method to find a Value given an ast.Expr; use + // ValueForExpr instead. + Source() ast.Node + + // Pos returns Source().Pos() if Source is not nil, else it + // returns token.NoPos. Pos() token.Pos } -// An Instruction is an SSA instruction that computes a new Value or +// An Instruction is an IR instruction that computes a new Value or // has some effect. // // An Instruction that defines a value (e.g. BinOp) also implements @@ -166,23 +181,36 @@ type Value interface { // does not. // type Instruction interface { + setSource(ast.Node) + setID(ID) + // String returns the disassembled form of this value. // // Examples of Instructions that are Values: - // "x + y" (BinOp) - // "len([])" (Call) + // "BinOp {+} t1 t2" (BinOp) + // "Call len t1" (Call) // Note that the name of the Value is not printed. // // Examples of Instructions that are not Values: - // "return x" (Return) - // "*y = x" (Store) + // "Return t1" (Return) + // "Store {int} t2 t1" (Store) // - // (The separation Value.Name() from Value.String() is useful + // (The separation of Value.Name() from Value.String() is useful // for some analyses which distinguish the operation from the // value it defines, e.g., 'y = local int' is both an allocation // of memory 'local int' and a definition of a pointer y.) String() string + // ID returns the ID of this instruction. IDs are unique within a single + // function and are densely numbered, but may contain gaps. + // Globally, instructions are identified by their addresses. However, + // IDs exist to facilitate efficient storage of mappings between + // instructions and data when analysing functions. + // + // NB: IDs are allocated late in the IR construction process and + // are not available to early stages of said process. + ID() ID + // Parent returns the function to which this instruction // belongs. Parent() *Function @@ -212,39 +240,37 @@ type Instruction interface { // Values.) Operands(rands []*Value) []*Value - // Pos returns the location of the AST token most closely - // associated with the operation that gave rise to this - // instruction, or token.NoPos if it was not explicit in the - // source. - // - // For each ast.Node type, a particular token is designated as - // the closest location for the expression, e.g. the Go token - // for an *ast.GoStmt. This permits a compact but approximate - // mapping from Instructions to source positions for use in - // diagnostic messages, for example. - // - // (Do not use this position to determine which Instruction - // corresponds to an ast.Expr; see the notes for Value.Pos. - // This position may be used to determine which non-Value - // Instruction corresponds to some ast.Stmts, but not all: If - // and Jump instructions have no Pos(), for example.) + Referrers() *[]Instruction // nil for non-Values + + // Source returns the AST node responsible for creating this + // instruction. A single AST node may be responsible for more than + // one instruction, and not all instructions have an associated + // AST node. + Source() ast.Node + + // Pos returns Source().Pos() if Source is not nil, else it + // returns token.NoPos. Pos() token.Pos } -// A Node is a node in the SSA value graph. Every concrete type that +// A Node is a node in the IR value graph. Every concrete type that // implements Node is also either a Value, an Instruction, or both. // // Node contains the methods common to Value and Instruction, plus the // Operands and Referrers methods generalized to return nil for // non-Instructions and non-Values, respectively. // -// Node is provided to simplify SSA graph algorithms. Clients should +// Node is provided to simplify IR graph algorithms. Clients should // use the more specific and informative Value or Instruction // interfaces where appropriate. // type Node interface { + setID(ID) + // Common methods: + ID() ID String() string + Source() ast.Node Pos() token.Pos Parent() *Function @@ -267,11 +293,6 @@ type Node interface { // the disassembly. // To iterate over the blocks in dominance order, use DomPreorder(). // -// Recover is an optional second entry point to which control resumes -// after a recovered panic. The Recover block may contain only a return -// statement, preceded by a load of the function's named return -// parameters, if any. -// // A nested function (Parent()!=nil) that refers to one or more // lexically enclosing local variables ("free variables") has FreeVars. // Such functions cannot be called directly but require a @@ -294,44 +315,63 @@ type Node interface { // Type() returns the function's Signature. // type Function struct { + node + name string object types.Object // a declared *types.Func or one of its wrappers method *types.Selection // info about provenance of synthetic methods Signature *types.Signature - pos token.Pos - Synthetic string // provenance of synthetic function; "" for true source functions - syntax ast.Node // *ast.Func{Decl,Lit}; replaced with simple ast.Node after build, unless debug mode - parent *Function // enclosing function if anon; nil if global - Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) - Prog *Program // enclosing program - Params []*Parameter // function parameters; for methods, includes receiver - FreeVars []*FreeVar // free variables whose values must be supplied by closure - Locals []*Alloc // local variables of this function - Blocks []*BasicBlock // basic blocks of the function; nil => external - Recover *BasicBlock // optional; control transfers here after recovered panic - AnonFuncs []*Function // anonymous functions directly beneath this one - referrers []Instruction // referring instructions (iff Parent() != nil) + Synthetic string // provenance of synthetic function; "" for true source functions + parent *Function // enclosing function if anon; nil if global + Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) + Prog *Program // enclosing program + Params []*Parameter // function parameters; for methods, includes receiver + FreeVars []*FreeVar // free variables whose values must be supplied by closure + Locals []*Alloc // local variables of this function + Blocks []*BasicBlock // basic blocks of the function; nil => external + Exit *BasicBlock // The function's exit block + AnonFuncs []*Function // anonymous functions directly beneath this one + referrers []Instruction // referring instructions (iff Parent() != nil) + WillExit bool // Calling this function will always terminate the process + WillUnwind bool // Calling this function will always unwind (it will call runtime.Goexit or panic) - // The following fields are set transiently during building, - // then cleared. - currentBlock *BasicBlock // where to emit code - objects map[types.Object]Value // addresses of local variables - namedResults []*Alloc // tuple of named results - targets *targets // linked stack of branch targets - lblocks map[*ast.Object]*lblock // labelled blocks + *functionBody } -// BasicBlock represents an SSA basic block. +type functionBody struct { + // The following fields are set transiently during building, + // then cleared. + currentBlock *BasicBlock // where to emit code + objects map[types.Object]Value // addresses of local variables + namedResults []*Alloc // tuple of named results + implicitResults []*Alloc // tuple of results + targets *targets // linked stack of branch targets + lblocks map[*ast.Object]*lblock // labelled blocks + consts []*Const + wr *HTMLWriter + fakeExits BlockSet + blocksets [5]BlockSet + hasDefer bool +} + +func (fn *Function) results() []*Alloc { + if len(fn.namedResults) > 0 { + return fn.namedResults + } + return fn.implicitResults +} + +// BasicBlock represents an IR basic block. // // The final element of Instrs is always an explicit transfer of -// control (If, Jump, Return, or Panic). +// control (If, Jump, Return, Panic, or Unreachable). // // A block may contain no Instructions only if it is unreachable, // i.e., Preds is nil. Empty blocks are typically pruned. // // BasicBlocks and their Preds/Succs relation form a (possibly cyclic) -// graph independent of the SSA Value graph: the control-flow graph or +// graph independent of the IR Value graph: the control-flow graph or // CFG. It is illegal for multiple edges to exist between the same // pair of blocks. // @@ -350,8 +390,10 @@ type BasicBlock struct { Preds, Succs []*BasicBlock // predecessors and successors succs2 [2]*BasicBlock // initial space for Succs dom domInfo // dominator tree info - gaps int // number of nil Instrs (transient) - rundefers int // number of rundefers (transient) + pdom domInfo // post-dominator tree info + post int + gaps int // number of nil Instrs (transient) + rundefers int // number of rundefers (transient) } // Pure values ---------------------------------------- @@ -373,9 +415,10 @@ type BasicBlock struct { // belongs to an enclosing function. // type FreeVar struct { + node + name string typ types.Type - pos token.Pos parent *Function referrers []Instruction @@ -386,12 +429,10 @@ type FreeVar struct { // A Parameter represents an input parameter of a function. // type Parameter struct { - name string - object types.Object // a *types.Var; nil for non-source locals - typ types.Type - pos token.Pos - parent *Function - referrers []Instruction + register + + name string + object types.Object // a *types.Var; nil for non-source locals } // A Const represents the value of a constant expression. @@ -411,12 +452,13 @@ type Parameter struct { // Pos() returns token.NoPos. // // Example printed form: -// 42:int -// "hello":untyped string -// 3+4i:MyComplex +// Const {42} +// Const {"test"} +// Const {(3 + 4i)} // type Const struct { - typ types.Type + register + Value constant.Value } @@ -427,10 +469,11 @@ type Const struct { // identifier. // type Global struct { + node + name string object types.Object // a *types.Var; may be nil for synthetics e.g. init$guard typ types.Type - pos token.Pos Pkg *Package } @@ -441,12 +484,12 @@ type Global struct { // Builtins can only appear in CallCommon.Func. // // Name() indicates the function: one of the built-in functions from the -// Go spec (excluding "make" and "new") or one of these ssa-defined +// Go spec (excluding "make" and "new") or one of these ir-defined // intrinsics: // // // wrapnilchk returns ptr if non-nil, panics otherwise. // // (For use in indirection wrappers.) -// func ssa:wrapnilchk(ptr *T, recvType, methodName string) *T +// func ir:wrapnilchk(ptr *T, recvType, methodName string) *T // // Object() returns a *types.Builtin for built-ins defined by the spec, // nil for others. @@ -455,6 +498,8 @@ type Global struct { // signature of the built-in for this call. // type Builtin struct { + node + name string sig *types.Signature } @@ -470,12 +515,12 @@ type Builtin struct { // // If Heap is false, Alloc allocates space in the function's // activation record (frame); we refer to an Alloc(Heap=false) as a -// "local" alloc. Each local Alloc returns the same address each time +// "stack" alloc. Each stack Alloc returns the same address each time // it is executed within the same activation; the space is // re-initialized to zero. // // If Heap is true, Alloc allocates space in the heap; we -// refer to an Alloc(Heap=true) as a "new" alloc. Each new Alloc +// refer to an Alloc(Heap=true) as a "heap" alloc. Each heap Alloc // returns a different address each time it is executed. // // When Alloc is applied to a channel, map or slice type, it returns @@ -488,44 +533,43 @@ type Builtin struct { // allocates a varargs slice. // // Example printed form: -// t0 = local int -// t1 = new int +// t1 = StackAlloc <*int> +// t2 = HeapAlloc <*int> (new) // type Alloc struct { register - Comment string - Heap bool - index int // dense numbering; for lifting + Heap bool + index int // dense numbering; for lifting } var _ Instruction = (*Sigma)(nil) var _ Value = (*Sigma)(nil) +// The Sigma instruction represents an SSI σ-node, which splits values +// at branches in the control flow. +// +// Conceptually, σ-nodes exist at the end of blocks that branch and +// constitute parallel assignments to one value per destination block. +// However, such a representation would be awkward to work with, so +// instead we place σ-nodes at the beginning of branch targets. The +// From field denotes to which incoming edge the node applies. +// +// Within a block, all σ-nodes must appear before all non-σ nodes. +// +// Example printed form: +// t2 = Sigma [#0] t1 (x) +// type Sigma struct { register - X Value - Branch bool -} + From *BasicBlock + X Value -func (p *Sigma) Value() Value { - v := p.X - for { - sigma, ok := v.(*Sigma) - if !ok { - break - } - v = sigma - } - return v -} - -func (p *Sigma) String() string { - return fmt.Sprintf("σ [%s.%t]", relName(p.X, p), p.Branch) + live bool // used during lifting } // The Phi instruction represents an SSA φ-node, which combines values // that differ across incoming control-flow edges and yields a new -// value. Within a block, all φ-nodes must appear before all non-φ +// value. Within a block, all φ-nodes must appear before all non-φ, non-σ // nodes. // // Pos() returns the position of the && or || for short-circuit @@ -533,12 +577,13 @@ func (p *Sigma) String() string { // during SSA renaming. // // Example printed form: -// t2 = phi [0: t0, 1: t1] +// t3 = Phi 2:t1 4:t2 (x) // type Phi struct { register - Comment string // a hint as to its purpose - Edges []Value // Edges[i] is value for Block().Preds[i] + Edges []Value // Edges[i] is value for Block().Preds[i] + + live bool // used during lifting } // The Call instruction represents a function or method call. @@ -552,9 +597,9 @@ type Phi struct { // Pos() returns the ast.CallExpr.Lparen, if explicit in the source. // // Example printed form: -// t2 = println(t0, t1) -// t4 = t3() -// t7 = invoke t5.Println(...t6) +// t3 = Call <()> println t1 t2 +// t4 = Call <()> foo$1 +// t6 = Invoke t5.String // type Call struct { register @@ -566,7 +611,7 @@ type Call struct { // Pos() returns the ast.BinaryExpr.OpPos, if explicit in the source. // // Example printed form: -// t1 = t0 + 1:int +// t3 = BinOp {+} t2 t1 // type BinOp struct { register @@ -579,32 +624,32 @@ type BinOp struct { } // The UnOp instruction yields the result of Op X. -// ARROW is channel receive. -// MUL is pointer indirection (load). // XOR is bitwise complement. // SUB is negation. // NOT is logical negation. // -// If CommaOk and Op=ARROW, the result is a 2-tuple of the value above -// and a boolean indicating the success of the receive. The -// components of the tuple are accessed using Extract. // -// Pos() returns the ast.UnaryExpr.OpPos, if explicit in the source. -// For receive operations (ARROW) implicit in ranging over a channel, -// Pos() returns the ast.RangeStmt.For. -// For implicit memory loads (STAR), Pos() returns the position of the +// Example printed form: +// t2 = UnOp {^} t1 +// +type UnOp struct { + register + Op token.Token // One of: NOT SUB XOR ! - ^ + X Value +} + +// The Load instruction loads a value from a memory address. +// +// For implicit memory loads, Pos() returns the position of the // most closely associated source-level construct; the details are not // specified. // // Example printed form: -// t0 = *x -// t2 = <-t1,ok +// t2 = Load t1 // -type UnOp struct { +type Load struct { register - Op token.Token // One of: NOT SUB ARROW MUL XOR ! - <- * ^ - X Value - CommaOk bool + X Value } // The ChangeType instruction applies to X a value-preserving type @@ -623,7 +668,7 @@ type UnOp struct { // from an explicit conversion in the source. // // Example printed form: -// t1 = changetype *int <- IntPtr (t0) +// t2 = ChangeType <*T> t1 // type ChangeType struct { register @@ -646,13 +691,13 @@ type ChangeType struct { // This operation cannot fail dynamically. // // Conversions of untyped string/number/bool constants to a specific -// representation are eliminated during SSA construction. +// representation are eliminated during IR construction. // // Pos() returns the ast.CallExpr.Lparen, if the instruction arose // from an explicit conversion in the source. // // Example printed form: -// t1 = convert []byte <- string (t0) +// t2 = Convert <[]byte> t1 // type Convert struct { register @@ -669,7 +714,7 @@ type Convert struct { // otherwise. // // Example printed form: -// t1 = change interface interface{} <- I (t0) +// t2 = ChangeInterface t1 // type ChangeInterface struct { register @@ -689,8 +734,7 @@ type ChangeInterface struct { // from an explicit conversion in the source. // // Example printed form: -// t1 = make interface{} <- int (42:int) -// t2 = make Stringer <- t0 +// t2 = MakeInterface t1 // type MakeInterface struct { register @@ -706,8 +750,8 @@ type MakeInterface struct { // closure or the ast.SelectorExpr.Sel for a bound method closure. // // Example printed form: -// t0 = make closure anon@1.2 [x y z] -// t1 = make closure bound$(main.I).add [i] +// t1 = MakeClosure foo$1 t1 t2 +// t5 = MakeClosure (T).foo$bound t4 // type MakeClosure struct { register @@ -724,8 +768,8 @@ type MakeClosure struct { // the ast.CompositeLit.Lbrack if created by a literal. // // Example printed form: -// t1 = make map[string]int t0 -// t1 = make StringIntMap t0 +// t1 = MakeMap +// t2 = MakeMap t1 // type MakeMap struct { register @@ -741,8 +785,8 @@ type MakeMap struct { // created it. // // Example printed form: -// t0 = make chan int 0 -// t0 = make IntChan 0 +// t3 = MakeChan t1 +// t4 = MakeChan t2 // type MakeChan struct { register @@ -763,8 +807,8 @@ type MakeChan struct { // created it. // // Example printed form: -// t1 = make []string 1:int t0 -// t1 = make StringSlice 1:int t0 +// t3 = MakeSlice <[]string> t1 t2 +// t4 = MakeSlice t1 t2 // type MakeSlice struct { register @@ -786,7 +830,7 @@ type MakeSlice struct { // NoPos if not explicit in the source (e.g. a variadic argument slice). // // Example printed form: -// t1 = slice t0[1:] +// t4 = Slice <[]int> t3 t2 t1 // type Slice struct { register @@ -808,7 +852,7 @@ type Slice struct { // field, if explicit in the source. // // Example printed form: -// t1 = &t0.name [#1] +// t2 = FieldAddr <*int> [0] (X) t1 // type FieldAddr struct { register @@ -826,7 +870,7 @@ type FieldAddr struct { // field, if explicit in the source. // // Example printed form: -// t1 = t0.name [#1] +// t2 = FieldAddr [0] (X) t1 // type Field struct { register @@ -837,7 +881,7 @@ type Field struct { // The IndexAddr instruction yields the address of the element at // index Index of collection X. Index is an integer expression. // -// The elements of maps and strings are not addressable; use Lookup or +// The elements of maps and strings are not addressable; use StringLookup, MapLookup or // MapUpdate instead. // // Dynamically, this instruction panics if X evaluates to a nil *array @@ -849,7 +893,7 @@ type Field struct { // explicit in the source. // // Example printed form: -// t2 = &t0[t1] +// t3 = IndexAddr <*int> t2 t1 // type IndexAddr struct { register @@ -863,7 +907,7 @@ type IndexAddr struct { // explicit in the source. // // Example printed form: -// t2 = t0[t1] +// t3 = Index t2 t1 // type Index struct { register @@ -871,9 +915,7 @@ type Index struct { Index Value // integer index } -// The Lookup instruction yields element Index of collection X, a map -// or string. Index is an integer expression if X is a string or the -// appropriate key type if X is a map. +// The MapLookup instruction yields element Index of collection X, a map. // // If CommaOk, the result is a 2-tuple of the value above and a // boolean indicating the result of a map membership test for the key. @@ -882,16 +924,30 @@ type Index struct { // Pos() returns the ast.IndexExpr.Lbrack, if explicit in the source. // // Example printed form: -// t2 = t0[t1] -// t5 = t3[t4],ok +// t4 = MapLookup t3 t1 +// t6 = MapLookup <(string, bool)> t3 t2 // -type Lookup struct { +type MapLookup struct { register - X Value // string or map - Index Value // numeric or key-typed index + X Value // map + Index Value // key-typed index CommaOk bool // return a value,ok pair } +// The StringLookup instruction yields element Index of collection X, a string. +// Index is an integer expression. +// +// Pos() returns the ast.IndexExpr.Lbrack, if explicit in the source. +// +// Example printed form: +// t3 = StringLookup t2 t1 +// +type StringLookup struct { + register + X Value // string + Index Value // numeric index +} + // SelectState is a helper for Select. // It represents one goal state and its corresponding communication. // @@ -906,10 +962,10 @@ type SelectState struct { // The Select instruction tests whether (or blocks until) one // of the specified sent or received states is entered. // -// Let n be the number of States for which Dir==RECV and T_i (0<=i [<-t4, t5<-t1] +// t11 = SelectBlocking <(index int, ok bool)> [] // type Select struct { register @@ -954,7 +1010,7 @@ type Select struct { // Pos() returns the ast.RangeStmt.For. // // Example printed form: -// t0 = range "hello":string +// t2 = Range t1 // type Range struct { register @@ -977,7 +1033,8 @@ type Range struct { // The types of k and/or v may be types.Invalid. // // Example printed form: -// t1 = next t0 +// t5 = Next <(ok bool, k int, v rune)> t2 +// t5 = Next <(ok bool, k invalid type, v invalid type)> t2 // type Next struct { register @@ -1017,8 +1074,8 @@ type Next struct { // type-switch statement. // // Example printed form: -// t1 = typeassert t0.(int) -// t3 = typeassert,ok t2.(T) +// t2 = TypeAssert t1 +// t4 = TypeAssert <(value fmt.Stringer, ok bool)> t1 // type TypeAssert struct { register @@ -1030,11 +1087,11 @@ type TypeAssert struct { // The Extract instruction yields component Index of Tuple. // // This is used to access the results of instructions with multiple -// return values, such as Call, TypeAssert, Next, UnOp(ARROW) and -// IndexExpr(Map). +// return values, such as Call, TypeAssert, Next, Recv, +// MapLookup and others. // // Example printed form: -// t1 = extract t0 #1 +// t7 = Extract [1] (ok) t4 // type Extract struct { register @@ -1052,10 +1109,28 @@ type Extract struct { // Pos() returns NoPos. // // Example printed form: -// jump done +// Jump → b1 // type Jump struct { anInstruction + Comment string +} + +// The Unreachable pseudo-instruction signals that execution cannot +// continue after the preceding function call because it terminates +// the process. +// +// The instruction acts as a control instruction, jumping to the exit +// block. However, this jump will never execute. +// +// An Unreachable instruction must be the last instruction of its +// containing BasicBlock. +// +// Example printed form: +// Unreachable → b1 +// +type Unreachable struct { + anInstruction } // The If instruction transfers control to one of the two successors @@ -1065,16 +1140,30 @@ type Jump struct { // An If instruction must be the last instruction of its containing // BasicBlock. // -// Pos() returns NoPos. +// Pos() returns the *ast.IfStmt, if explicit in the source. // // Example printed form: -// if t0 goto done else body +// If t2 → b1 b2 // type If struct { anInstruction Cond Value } +type ConstantSwitch struct { + anInstruction + Tag Value + // Constant branch conditions. A nil Value denotes the (implicit + // or explicit) default branch. + Conds []Value +} + +type TypeSwitch struct { + register + Tag Value + Conds []types.Type +} + // The Return instruction returns values and control back to the calling // function. // @@ -1085,7 +1174,7 @@ type If struct { // components which the caller must access using Extract instructions. // // There is no instruction to return a ready-made tuple like those -// returned by a "value,ok"-mode TypeAssert, Lookup or UnOp(ARROW) or +// returned by a "value,ok"-mode TypeAssert, MapLookup or Recv or // a tail-call to a function with multiple result parameters. // // Return must be the last instruction of its containing BasicBlock. @@ -1094,13 +1183,12 @@ type If struct { // Pos() returns the ast.ReturnStmt.Return, if explicit in the source. // // Example printed form: -// return -// return nil:I, 2:int +// Return +// Return t1 t2 // type Return struct { anInstruction Results []Value - pos token.Pos } // The RunDefers instruction pops and invokes the entire stack of @@ -1113,7 +1201,7 @@ type Return struct { // Pos() returns NoPos. // // Example printed form: -// rundefers +// RunDefers // type RunDefers struct { anInstruction @@ -1122,7 +1210,7 @@ type RunDefers struct { // The Panic instruction initiates a panic with value X. // // A Panic instruction must be the last instruction of its containing -// BasicBlock, which must have no successors. +// BasicBlock, which must have one successor, the exit block. // // NB: 'go panic(x)' and 'defer panic(x)' do not use this instruction; // they are treated as calls to a built-in function. @@ -1131,12 +1219,11 @@ type RunDefers struct { // in the source. // // Example printed form: -// panic t0 +// Panic t1 // type Panic struct { anInstruction - X Value // an interface{} - pos token.Pos + X Value // an interface{} } // The Go instruction creates a new goroutine and calls the specified @@ -1147,14 +1234,13 @@ type Panic struct { // Pos() returns the ast.GoStmt.Go. // // Example printed form: -// go println(t0, t1) -// go t3() -// go invoke t5.Println(...t6) +// Go println t1 +// Go t3 +// GoInvoke t4.Bar t2 // type Go struct { anInstruction Call CallCommon - pos token.Pos } // The Defer instruction pushes the specified call onto a stack of @@ -1165,14 +1251,13 @@ type Go struct { // Pos() returns the ast.DeferStmt.Defer. // // Example printed form: -// defer println(t0, t1) -// defer t3() -// defer invoke t5.Println(...t6) +// Defer println t1 +// Defer t3 +// DeferInvoke t4.Bar t2 // type Defer struct { anInstruction Call CallCommon - pos token.Pos } // The Send instruction sends X on channel Chan. @@ -1180,12 +1265,30 @@ type Defer struct { // Pos() returns the ast.SendStmt.Arrow, if explicit in the source. // // Example printed form: -// send t0 <- t1 +// Send t2 t1 // type Send struct { anInstruction Chan, X Value - pos token.Pos +} + +// The Recv instruction receives from channel Chan. +// +// If CommaOk, the result is a 2-tuple of the value above +// and a boolean indicating the success of the receive. The +// components of the tuple are accessed using Extract. +// +// Pos() returns the ast.UnaryExpr.OpPos, if explicit in the source. +// For receive operations implicit in ranging over a channel, +// Pos() returns the ast.RangeStmt.For. +// +// Example printed form: +// t2 = Recv t1 +// t3 = Recv <(int, bool)> t1 +type Recv struct { + register + Chan Value + CommaOk bool } // The Store instruction stores Val at address Addr. @@ -1197,13 +1300,12 @@ type Send struct { // implementation choices, the details are not specified. // // Example printed form: -// *x = y +// Store {int} t2 t1 // type Store struct { anInstruction Addr Value Val Value - pos token.Pos } // The BlankStore instruction is emitted for assignments to the blank @@ -1214,7 +1316,7 @@ type Store struct { // Pos() returns NoPos. // // Example printed form: -// _ = t0 +// BlankStore t1 // type BlankStore struct { anInstruction @@ -1228,18 +1330,17 @@ type BlankStore struct { // if explicit in the source. // // Example printed form: -// t0[t1] = t2 +// MapUpdate t3 t1 t2 // type MapUpdate struct { anInstruction Map Value Key Value Value Value - pos token.Pos } // A DebugRef instruction maps a source-level expression Expr to the -// SSA value X that represents the value (!IsAddr) or address (IsAddr) +// IR value X that represents the value (!IsAddr) or address (IsAddr) // of that expression. // // DebugRef is a pseudo-instruction: it has no dynamic effect. @@ -1249,11 +1350,6 @@ type MapUpdate struct { // documented at Value.Pos(). e.g. CallExpr.Pos() does not return the // position of the ("designated") Lparen token. // -// If Expr is an *ast.Ident denoting a var or func, Object() returns -// the object; though this information can be obtained from the type -// checker, including it here greatly facilitates debugging. -// For non-Ident expressions, Object() returns nil. -// // DebugRefs are generated only for functions built with debugging // enabled; see Package.SetDebugMode() and the GlobalDebug builder // mode flag. @@ -1281,30 +1377,42 @@ type DebugRef struct { // Embeddable mix-ins and helpers for common parts of other structs. ----------- -// register is a mix-in embedded by all SSA values that are also +// register is a mix-in embedded by all IR values that are also // instructions, i.e. virtual registers, and provides a uniform // implementation of most of the Value interface: Value.Name() is a // numbered register (e.g. "t0"); the other methods are field accessors. // // Temporary names are automatically assigned to each register on -// completion of building a function in SSA form. -// -// Clients must not assume that the 'id' value (and the Name() derived -// from it) is unique within a function. As always in this API, -// semantics are determined only by identity; names exist only to -// facilitate debugging. +// completion of building a function in IR form. // type register struct { anInstruction - num int // "name" of virtual register, e.g. "t0". Not guaranteed unique. typ types.Type // type of virtual register - pos token.Pos // position of source expression, or NoPos referrers []Instruction } +type node struct { + source ast.Node + id ID +} + +func (n *node) setID(id ID) { n.id = id } +func (n node) ID() ID { return n.id } + +func (n *node) setSource(source ast.Node) { n.source = source } +func (n *node) Source() ast.Node { return n.source } + +func (n *node) Pos() token.Pos { + if n.source != nil { + return n.source.Pos() + } + return token.NoPos +} + // anInstruction is a mix-in embedded by all Instructions. // It provides the implementations of the Block and setBlock methods. type anInstruction struct { + node block *BasicBlock // the basic block of this instruction } @@ -1336,9 +1444,9 @@ type anInstruction struct { // Args[0] contains the receiver parameter. // // Example printed form: -// t2 = println(t0, t1) -// go t3() -// defer t5(...t6) +// t3 = Call <()> println t1 t2 +// Go t3 +// Defer t3 // // 2. "invoke" mode: when Method is non-nil (IsInvoke), a CallCommon // represents a dynamically dispatched call to an interface method. @@ -1352,18 +1460,18 @@ type anInstruction struct { // receiver but the first true argument. // // Example printed form: -// t1 = invoke t0.String() -// go invoke t3.Run(t2) -// defer invoke t4.Handle(...t5) +// t6 = Invoke t5.String +// GoInvoke t4.Bar t2 +// DeferInvoke t4.Bar t2 // // For all calls to variadic functions (Signature().Variadic()), // the last element of Args is a slice. // type CallCommon struct { - Value Value // receiver (invoke mode) or func value (call mode) - Method *types.Func // abstract method (invoke mode) - Args []Value // actual parameters (in static method call, includes receiver) - pos token.Pos // position of CallExpr.Lparen, iff explicit in source + Value Value // receiver (invoke mode) or func value (call mode) + Method *types.Func // abstract method (invoke mode) + Args []Value // actual parameters (in static method call, includes receiver) + Results Value } // IsInvoke returns true if this call has "invoke" (not "call") mode. @@ -1371,8 +1479,6 @@ func (c *CallCommon) IsInvoke() bool { return c.Method != nil } -func (c *CallCommon) Pos() token.Pos { return c.pos } - // Signature returns the signature of the called function. // // For an "invoke"-mode call, the signature of the interface method is @@ -1427,7 +1533,7 @@ func (c *CallCommon) Description() string { type CallInstruction interface { Instruction Common() *CallCommon // returns the common parts of the call - Value() *Call // returns the result value of the call (*Call) or nil (*Go, *Defer) + Value() *Call } func (s *Call) Common() *CallCommon { return &s.Call } @@ -1448,13 +1554,11 @@ func (v *Builtin) Parent() *Function { return nil } func (v *FreeVar) Type() types.Type { return v.typ } func (v *FreeVar) Name() string { return v.name } func (v *FreeVar) Referrers() *[]Instruction { return &v.referrers } -func (v *FreeVar) Pos() token.Pos { return v.pos } func (v *FreeVar) Parent() *Function { return v.parent } func (v *Global) Type() types.Type { return v.typ } func (v *Global) Name() string { return v.name } func (v *Global) Parent() *Function { return nil } -func (v *Global) Pos() token.Pos { return v.pos } func (v *Global) Referrers() *[]Instruction { return nil } func (v *Global) Token() token.Token { return token.VAR } func (v *Global) Object() types.Object { return v.object } @@ -1464,7 +1568,6 @@ func (v *Global) RelString(from *types.Package) string { return relString(v, fro func (v *Function) Name() string { return v.name } func (v *Function) Type() types.Type { return v.Signature } -func (v *Function) Pos() token.Pos { return v.pos } func (v *Function) Token() token.Token { return token.FUNC } func (v *Function) Object() types.Object { return v.object } func (v *Function) String() string { return v.RelString(nil) } @@ -1477,24 +1580,15 @@ func (v *Function) Referrers() *[]Instruction { return nil } -func (v *Parameter) Type() types.Type { return v.typ } -func (v *Parameter) Name() string { return v.name } -func (v *Parameter) Object() types.Object { return v.object } -func (v *Parameter) Referrers() *[]Instruction { return &v.referrers } -func (v *Parameter) Pos() token.Pos { return v.pos } -func (v *Parameter) Parent() *Function { return v.parent } +func (v *Parameter) Object() types.Object { return v.object } func (v *Alloc) Type() types.Type { return v.typ } func (v *Alloc) Referrers() *[]Instruction { return &v.referrers } -func (v *Alloc) Pos() token.Pos { return v.pos } func (v *register) Type() types.Type { return v.typ } func (v *register) setType(typ types.Type) { v.typ = typ } -func (v *register) Name() string { return fmt.Sprintf("t%d", v.num) } -func (v *register) setNum(num int) { v.num = num } +func (v *register) Name() string { return fmt.Sprintf("t%d", v.id) } func (v *register) Referrers() *[]Instruction { return &v.referrers } -func (v *register) Pos() token.Pos { return v.pos } -func (v *register) setPos(pos token.Pos) { v.pos = pos } func (v *anInstruction) Parent() *Function { return v.block.parent } func (v *anInstruction) Block() *BasicBlock { return v.block } @@ -1551,19 +1645,7 @@ func (p *Package) Type(name string) (t *Type) { return } -func (v *Call) Pos() token.Pos { return v.Call.pos } -func (s *Defer) Pos() token.Pos { return s.pos } -func (s *Go) Pos() token.Pos { return s.pos } -func (s *MapUpdate) Pos() token.Pos { return s.pos } -func (s *Panic) Pos() token.Pos { return s.pos } -func (s *Return) Pos() token.Pos { return s.pos } -func (s *Send) Pos() token.Pos { return s.pos } -func (s *Store) Pos() token.Pos { return s.pos } -func (s *BlankStore) Pos() token.Pos { return token.NoPos } -func (s *If) Pos() token.Pos { return token.NoPos } -func (s *Jump) Pos() token.Pos { return token.NoPos } -func (s *RunDefers) Pos() token.Pos { return token.NoPos } -func (s *DebugRef) Pos() token.Pos { return s.Expr.Pos() } +func (s *DebugRef) Pos() token.Pos { return s.Expr.Pos() } // Operands. @@ -1627,6 +1709,19 @@ func (s *If) Operands(rands []*Value) []*Value { return append(rands, &s.Cond) } +func (s *ConstantSwitch) Operands(rands []*Value) []*Value { + rands = append(rands, &s.Tag) + for i := range s.Conds { + rands = append(rands, &s.Conds[i]) + } + return rands +} + +func (s *TypeSwitch) Operands(rands []*Value) []*Value { + rands = append(rands, &s.Tag) + return rands +} + func (v *Index) Operands(rands []*Value) []*Value { return append(rands, &v.X, &v.Index) } @@ -1639,7 +1734,15 @@ func (*Jump) Operands(rands []*Value) []*Value { return rands } -func (v *Lookup) Operands(rands []*Value) []*Value { +func (*Unreachable) Operands(rands []*Value) []*Value { + return rands +} + +func (v *MapLookup) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Index) +} + +func (v *StringLookup) Operands(rands []*Value) []*Value { return append(rands, &v.X, &v.Index) } @@ -1716,6 +1819,10 @@ func (s *Send) Operands(rands []*Value) []*Value { return append(rands, &s.Chan, &s.X) } +func (recv *Recv) Operands(rands []*Value) []*Value { + return append(rands, &recv.Chan) +} + func (v *Slice) Operands(rands []*Value) []*Value { return append(rands, &v.X, &v.Low, &v.High, &v.Max) } @@ -1736,6 +1843,10 @@ func (v *UnOp) Operands(rands []*Value) []*Value { return append(rands, &v.X) } +func (v *Load) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + // Non-Instruction Values: func (v *Builtin) Operands(rands []*Value) []*Value { return rands } func (v *FreeVar) Operands(rands []*Value) []*Value { return rands } diff --git a/vendor/honnef.co/go/tools/ssa/staticcheck.conf b/vendor/honnef.co/go/tools/ir/staticcheck.conf similarity index 100% rename from vendor/honnef.co/go/tools/ssa/staticcheck.conf rename to vendor/honnef.co/go/tools/ir/staticcheck.conf diff --git a/vendor/honnef.co/go/tools/ssa/util.go b/vendor/honnef.co/go/tools/ir/util.go similarity index 76% rename from vendor/honnef.co/go/tools/ssa/util.go rename to vendor/honnef.co/go/tools/ir/util.go index ddb1184609..df0f8bf971 100644 --- a/vendor/honnef.co/go/tools/ssa/util.go +++ b/vendor/honnef.co/go/tools/ir/util.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines a number of miscellaneous utility functions. @@ -52,36 +52,6 @@ func recvType(obj *types.Func) types.Type { return obj.Type().(*types.Signature).Recv().Type() } -// DefaultType returns the default "typed" type for an "untyped" type; -// it returns the incoming type for all other types. The default type -// for untyped nil is untyped nil. -// -// Exported to ssa/interp. -// -// TODO(adonovan): use go/types.DefaultType after 1.8. -// -func DefaultType(typ types.Type) types.Type { - if t, ok := typ.(*types.Basic); ok { - k := t.Kind() - switch k { - case types.UntypedBool: - k = types.Bool - case types.UntypedInt: - k = types.Int - case types.UntypedRune: - k = types.Rune - case types.UntypedFloat: - k = types.Float64 - case types.UntypedComplex: - k = types.Complex128 - case types.UntypedString: - k = types.String - } - typ = types.Typ[k] - } - return typ -} - // logStack prints the formatted "start" message to stderr and // returns a closure that prints the corresponding "end" message. // Call using 'defer logStack(...)()' to show builder stack on panic. diff --git a/vendor/honnef.co/go/tools/ssa/wrappers.go b/vendor/honnef.co/go/tools/ir/wrappers.go similarity index 89% rename from vendor/honnef.co/go/tools/ssa/wrappers.go rename to vendor/honnef.co/go/tools/ir/wrappers.go index a4ae71d8cf..7dd3347480 100644 --- a/vendor/honnef.co/go/tools/ssa/wrappers.go +++ b/vendor/honnef.co/go/tools/ir/wrappers.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ssa +package ir // This file defines synthesis of Functions that delegate to declared // methods; they come in three kinds: @@ -65,41 +65,42 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function { defer logStack("make %s to (%s)", description, recv.Type())() } fn := &Function{ - name: name, - method: sel, - object: obj, - Signature: sig, - Synthetic: description, - Prog: prog, - pos: obj.Pos(), + name: name, + method: sel, + object: obj, + Signature: sig, + Synthetic: description, + Prog: prog, + functionBody: new(functionBody), } + fn.initHTML(prog.PrintFunc) fn.startBody() - fn.addSpilledParam(recv) + fn.addSpilledParam(recv, nil) createParams(fn, start) indices := sel.Index() var v Value = fn.Locals[0] // spilled receiver if isPointer(sel.Recv()) { - v = emitLoad(fn, v) + v = emitLoad(fn, v, nil) // For simple indirection wrappers, perform an informative nil-check: // "value method (T).f called using nil *T pointer" if len(indices) == 1 && !isPointer(recvType(obj)) { var c Call c.Call.Value = &Builtin{ - name: "ssa:wrapnilchk", + name: "ir:wrapnilchk", sig: types.NewSignature(nil, types.NewTuple(anonVar(sel.Recv()), anonVar(tString), anonVar(tString)), types.NewTuple(anonVar(sel.Recv())), false), } c.Call.Args = []Value{ v, - stringConst(deref(sel.Recv()).String()), - stringConst(sel.Obj().Name()), + emitConst(fn, stringConst(deref(sel.Recv()).String())), + emitConst(fn, stringConst(sel.Obj().Name())), } c.setType(v.Type()) - v = fn.emit(&c) + v = fn.emit(&c, nil) } } @@ -111,7 +112,7 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function { // Load) in preference to value extraction (Field possibly // preceded by Load). - v = emitImplicitSelections(fn, v, indices[:len(indices)-1]) + v = emitImplicitSelections(fn, v, indices[:len(indices)-1], nil) // Invariant: v is a pointer, either // value of implicit *C field, or @@ -120,18 +121,18 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function { var c Call if r := recvType(obj); !isInterface(r) { // concrete method if !isPointer(r) { - v = emitLoad(fn, v) + v = emitLoad(fn, v, nil) } c.Call.Value = prog.declaredFunc(obj) c.Call.Args = append(c.Call.Args, v) } else { c.Call.Method = obj - c.Call.Value = emitLoad(fn, v) + c.Call.Value = emitLoad(fn, v, nil) } for _, arg := range fn.Params[1:] { c.Call.Args = append(c.Call.Args, arg) } - emitTailCall(fn, &c) + emitTailCall(fn, &c, nil) fn.finishBody() return fn } @@ -143,7 +144,7 @@ func makeWrapper(prog *Program, sel *types.Selection) *Function { func createParams(fn *Function, start int) { tparams := fn.Signature.Params() for i, n := start, tparams.Len(); i < n; i++ { - fn.addParamObj(tparams.At(i)) + fn.addParamObj(tparams.At(i), nil) } } @@ -184,13 +185,14 @@ func makeBound(prog *Program, obj *types.Func) *Function { defer logStack("%s", description)() } fn = &Function{ - name: obj.Name() + "$bound", - object: obj, - Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver - Synthetic: description, - Prog: prog, - pos: obj.Pos(), + name: obj.Name() + "$bound", + object: obj, + Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver + Synthetic: description, + Prog: prog, + functionBody: new(functionBody), } + fn.initHTML(prog.PrintFunc) fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn} fn.FreeVars = []*FreeVar{fv} @@ -208,7 +210,7 @@ func makeBound(prog *Program, obj *types.Func) *Function { for _, arg := range fn.Params { c.Call.Args = append(c.Call.Args, arg) } - emitTailCall(fn, &c) + emitTailCall(fn, &c, nil) fn.finishBody() prog.bounds[obj] = fn diff --git a/vendor/honnef.co/go/tools/ir/write.go b/vendor/honnef.co/go/tools/ir/write.go new file mode 100644 index 0000000000..b936bc9852 --- /dev/null +++ b/vendor/honnef.co/go/tools/ir/write.go @@ -0,0 +1,5 @@ +package ir + +func NewJump(parent *BasicBlock) *Jump { + return &Jump{anInstruction{block: parent}, ""} +} diff --git a/vendor/honnef.co/go/tools/lint/lint.go b/vendor/honnef.co/go/tools/lint/lint.go index de5a8f1288..1a70e0c298 100644 --- a/vendor/honnef.co/go/tools/lint/lint.go +++ b/vendor/honnef.co/go/tools/lint/lint.go @@ -3,6 +3,7 @@ package lint // import "honnef.co/go/tools/lint" import ( "bytes" + "encoding/gob" "fmt" "go/scanner" "go/token" @@ -17,6 +18,7 @@ import ( "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" "honnef.co/go/tools/config" + "honnef.co/go/tools/internal/cache" ) type Documentation struct { @@ -62,7 +64,7 @@ type LineIgnore struct { Line int Checks []string Matched bool - Pos token.Pos + Pos token.Position } func (li *LineIgnore) Match(p Problem) bool { @@ -119,6 +121,21 @@ type Problem struct { Message string Check string Severity Severity + Related []Related +} + +type Related struct { + Pos token.Position + End token.Position + Message string +} + +func (p Problem) Equal(o Problem) bool { + return p.Pos == o.Pos && + p.End == o.End && + p.Message == o.Message && + p.Check == o.Check && + p.Severity == o.Severity } func (p *Problem) String() string { @@ -132,6 +149,7 @@ type Linter struct { GoVersion int Config config.Config Stats Stats + RepeatAnalyzers uint } type CumulativeChecker interface { @@ -184,6 +202,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error return nil, err } r.goVersion = l.GoVersion + r.repeatAnalyzers = l.RepeatAnalyzers pkgs, err := r.Run(cfg, patterns, allowedAnalyzers, hasCumulative) if err != nil { @@ -264,10 +283,12 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error } atomic.StoreUint32(&r.stats.State, StateCumulative) - var problems []Problem for _, cum := range l.CumulativeCheckers { for _, res := range cum.Result() { pkg := tpkgToPkg[res.Pkg()] + if pkg == nil { + panic(fmt.Sprintf("analyzer %s flagged object %s in package %s, a package that we aren't tracking", cum.Analyzer(), res, res.Pkg())) + } allowedChecks := FilterChecks(allowedAnalyzers, pkg.cfg.Merge(l.Config).Checks) if allowedChecks[cum.Analyzer().Name] { pos := DisplayPosition(pkg.Fset, res.Pos()) @@ -278,21 +299,51 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error continue } p := cum.ProblemObject(pkg.Fset, res) - problems = append(problems, p) + pkg.problems = append(pkg.problems, p) } } } + for _, pkg := range pkgs { + if !pkg.fromSource { + // Don't cache packages that we loaded from the cache + continue + } + cpkg := cachedPackage{ + Problems: pkg.problems, + Ignores: pkg.ignores, + Config: pkg.cfg, + } + buf := &bytes.Buffer{} + if err := gob.NewEncoder(buf).Encode(cpkg); err != nil { + return nil, err + } + id := cache.Subkey(pkg.actionID, "data "+r.problemsCacheKey) + if err := r.cache.PutBytes(id, buf.Bytes()); err != nil { + return nil, err + } + } + + var problems []Problem + // Deduplicate line ignores. When U1000 processes a package and + // its test variant, it will only emit a single problem for an + // unused object, not two problems. We will, however, have two + // line ignores, one per package. Without deduplication, one line + // ignore will be marked as matched, while the other one won't, + // subsequently reporting a "this linter directive didn't match + // anything" error. + ignores := map[token.Position]Ignore{} for _, pkg := range pkgs { for _, ig := range pkg.ignores { - for i := range pkg.problems { - p := &pkg.problems[i] - if ig.Match(*p) { - p.Severity = Ignored + if lig, ok := ig.(*LineIgnore); ok { + ig = ignores[lig.Pos] + if ig == nil { + ignores[lig.Pos] = lig + ig = lig } } - for i := range problems { - p := &problems[i] + for i := range pkg.problems { + p := &pkg.problems[i] if ig.Match(*p) { p.Severity = Ignored } @@ -318,6 +369,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error if !ok { continue } + ig = ignores[ig.Pos].(*LineIgnore) if ig.Matched { continue } @@ -338,7 +390,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error continue } p := Problem{ - Pos: DisplayPosition(pkg.Fset, ig.Pos), + Pos: ig.Pos, Message: "this linter directive didn't match anything; should it be removed?", Check: "", } @@ -372,7 +424,7 @@ func (l *Linter) Lint(cfg *packages.Config, patterns []string) ([]Problem, error for i, p := range problems[1:] { // We may encounter duplicate problems because one file // can be part of many packages. - if problems[i] != p { + if !problems[i].Equal(p) { out = append(out, p) } } @@ -422,10 +474,6 @@ func FilterChecks(allChecks []*analysis.Analyzer, checks []string) map[string]bo return allowedChecks } -type Positioner interface { - Pos() token.Pos -} - func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position { if p == token.NoPos { return token.Position{} diff --git a/vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go b/vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go index 3b939e95f2..4408aff25e 100644 --- a/vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go +++ b/vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go @@ -4,283 +4,14 @@ package lintdsl import ( "bytes" - "flag" "fmt" "go/ast" - "go/constant" - "go/printer" - "go/token" - "go/types" - "strings" + "go/format" "golang.org/x/tools/go/analysis" - "honnef.co/go/tools/facts" - "honnef.co/go/tools/lint" - "honnef.co/go/tools/ssa" + "honnef.co/go/tools/pattern" ) -type packager interface { - Package() *ssa.Package -} - -func CallName(call *ssa.CallCommon) string { - if call.IsInvoke() { - return "" - } - switch v := call.Value.(type) { - case *ssa.Function: - fn, ok := v.Object().(*types.Func) - if !ok { - return "" - } - return lint.FuncName(fn) - case *ssa.Builtin: - return v.Name() - } - return "" -} - -func IsCallTo(call *ssa.CallCommon, name string) bool { return CallName(call) == name } -func IsType(T types.Type, name string) bool { return types.TypeString(T, nil) == name } - -func FilterDebug(instr []ssa.Instruction) []ssa.Instruction { - var out []ssa.Instruction - for _, ins := range instr { - if _, ok := ins.(*ssa.DebugRef); !ok { - out = append(out, ins) - } - } - return out -} - -func IsExample(fn *ssa.Function) bool { - if !strings.HasPrefix(fn.Name(), "Example") { - return false - } - f := fn.Prog.Fset.File(fn.Pos()) - if f == nil { - return false - } - return strings.HasSuffix(f.Name(), "_test.go") -} - -func IsPointerLike(T types.Type) bool { - switch T := T.Underlying().(type) { - case *types.Interface, *types.Chan, *types.Map, *types.Signature, *types.Pointer: - return true - case *types.Basic: - return T.Kind() == types.UnsafePointer - } - return false -} - -func IsIdent(expr ast.Expr, ident string) bool { - id, ok := expr.(*ast.Ident) - return ok && id.Name == ident -} - -// isBlank returns whether id is the blank identifier "_". -// If id == nil, the answer is false. -func IsBlank(id ast.Expr) bool { - ident, _ := id.(*ast.Ident) - return ident != nil && ident.Name == "_" -} - -func IsIntLiteral(expr ast.Expr, literal string) bool { - lit, ok := expr.(*ast.BasicLit) - return ok && lit.Kind == token.INT && lit.Value == literal -} - -// Deprecated: use IsIntLiteral instead -func IsZero(expr ast.Expr) bool { - return IsIntLiteral(expr, "0") -} - -func IsOfType(pass *analysis.Pass, expr ast.Expr, name string) bool { - return IsType(pass.TypesInfo.TypeOf(expr), name) -} - -func IsInTest(pass *analysis.Pass, node lint.Positioner) bool { - // FIXME(dh): this doesn't work for global variables with - // initializers - f := pass.Fset.File(node.Pos()) - return f != nil && strings.HasSuffix(f.Name(), "_test.go") -} - -func IsInMain(pass *analysis.Pass, node lint.Positioner) bool { - if node, ok := node.(packager); ok { - return node.Package().Pkg.Name() == "main" - } - return pass.Pkg.Name() == "main" -} - -func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string { - info := pass.TypesInfo - sel := info.Selections[expr] - if sel == nil { - if x, ok := expr.X.(*ast.Ident); ok { - pkg, ok := info.ObjectOf(x).(*types.PkgName) - if !ok { - // This shouldn't happen - return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name) - } - return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name) - } - panic(fmt.Sprintf("unsupported selector: %v", expr)) - } - return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) -} - -func IsNil(pass *analysis.Pass, expr ast.Expr) bool { - return pass.TypesInfo.Types[expr].IsNil() -} - -func BoolConst(pass *analysis.Pass, expr ast.Expr) bool { - val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() - return constant.BoolVal(val) -} - -func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool { - // We explicitly don't support typed bools because more often than - // not, custom bool types are used as binary enums and the - // explicit comparison is desired. - - ident, ok := expr.(*ast.Ident) - if !ok { - return false - } - obj := pass.TypesInfo.ObjectOf(ident) - c, ok := obj.(*types.Const) - if !ok { - return false - } - basic, ok := c.Type().(*types.Basic) - if !ok { - return false - } - if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { - return false - } - return true -} - -func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) { - tv := pass.TypesInfo.Types[expr] - if tv.Value == nil { - return 0, false - } - if tv.Value.Kind() != constant.Int { - return 0, false - } - return constant.Int64Val(tv.Value) -} - -func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) { - val := pass.TypesInfo.Types[expr].Value - if val == nil { - return "", false - } - if val.Kind() != constant.String { - return "", false - } - return constant.StringVal(val), true -} - -// Dereference returns a pointer's element type; otherwise it returns -// T. -func Dereference(T types.Type) types.Type { - if p, ok := T.Underlying().(*types.Pointer); ok { - return p.Elem() - } - return T -} - -// DereferenceR returns a pointer's element type; otherwise it returns -// T. If the element type is itself a pointer, DereferenceR will be -// applied recursively. -func DereferenceR(T types.Type) types.Type { - if p, ok := T.Underlying().(*types.Pointer); ok { - return DereferenceR(p.Elem()) - } - return T -} - -func IsGoVersion(pass *analysis.Pass, minor int) bool { - version := pass.Analyzer.Flags.Lookup("go").Value.(flag.Getter).Get().(int) - return version >= minor -} - -func CallNameAST(pass *analysis.Pass, call *ast.CallExpr) string { - switch fun := call.Fun.(type) { - case *ast.SelectorExpr: - fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func) - if !ok { - return "" - } - return lint.FuncName(fn) - case *ast.Ident: - obj := pass.TypesInfo.ObjectOf(fun) - switch obj := obj.(type) { - case *types.Func: - return lint.FuncName(obj) - case *types.Builtin: - return obj.Name() - default: - return "" - } - default: - return "" - } -} - -func IsCallToAST(pass *analysis.Pass, node ast.Node, name string) bool { - call, ok := node.(*ast.CallExpr) - if !ok { - return false - } - return CallNameAST(pass, call) == name -} - -func IsCallToAnyAST(pass *analysis.Pass, node ast.Node, names ...string) bool { - for _, name := range names { - if IsCallToAST(pass, node, name) { - return true - } - } - return false -} - -func Render(pass *analysis.Pass, x interface{}) string { - var buf bytes.Buffer - if err := printer.Fprint(&buf, pass.Fset, x); err != nil { - panic(err) - } - return buf.String() -} - -func RenderArgs(pass *analysis.Pass, args []ast.Expr) string { - var ss []string - for _, arg := range args { - ss = append(ss, Render(pass, arg)) - } - return strings.Join(ss, ", ") -} - -func Preamble(f *ast.File) string { - cutoff := f.Package - if f.Doc != nil { - cutoff = f.Doc.Pos() - } - var out []string - for _, cmt := range f.Comments { - if cmt.Pos() >= cutoff { - break - } - out = append(out, cmt.Text()) - } - return strings.Join(out, "\n") -} - func Inspect(node ast.Node, fn func(node ast.Node) bool) { if node == nil { return @@ -288,113 +19,40 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) { ast.Inspect(node, fn) } -func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec { - if len(specs) == 0 { - return nil +func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) { + // Note that we ignore q.Relevant – callers of Match usually use + // AST inspectors that already filter on nodes we're interested + // in. + m := &pattern.Matcher{TypesInfo: pass.TypesInfo} + ok := m.Match(q.Root, node) + return m, ok +} + +func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) { + m, ok := Match(pass, before, node) + if !ok { + return m, nil, false } - groups := make([][]ast.Spec, 1) - groups[0] = append(groups[0], specs[0]) + r := pattern.NodeToAST(after.Root, m.State) + buf := &bytes.Buffer{} + format.Node(buf, pass.Fset, r) + edit := []analysis.TextEdit{{ + Pos: node.Pos(), + End: node.End(), + NewText: buf.Bytes(), + }} + return m, edit, true +} - for _, spec := range specs[1:] { - g := groups[len(groups)-1] - if fset.PositionFor(spec.Pos(), false).Line-1 != - fset.PositionFor(g[len(g)-1].End(), false).Line { - - groups = append(groups, nil) - } - - groups[len(groups)-1] = append(groups[len(groups)-1], spec) +func Selector(x, sel string) *ast.SelectorExpr { + return &ast.SelectorExpr{ + X: &ast.Ident{Name: x}, + Sel: &ast.Ident{Name: sel}, } - - return groups } -func IsObject(obj types.Object, name string) bool { - var path string - if pkg := obj.Pkg(); pkg != nil { - path = pkg.Path() + "." - } - return path+obj.Name() == name -} - -type Field struct { - Var *types.Var - Tag string - Path []int -} - -// FlattenFields recursively flattens T and embedded structs, -// returning a list of fields. If multiple fields with the same name -// exist, all will be returned. -func FlattenFields(T *types.Struct) []Field { - return flattenFields(T, nil, nil) -} - -func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field { - if seen == nil { - seen = map[types.Type]bool{} - } - if seen[T] { - return nil - } - seen[T] = true - var out []Field - for i := 0; i < T.NumFields(); i++ { - field := T.Field(i) - tag := T.Tag(i) - np := append(path[:len(path):len(path)], i) - if field.Anonymous() { - if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok { - out = append(out, flattenFields(s, np, seen)...) - } - } else { - out = append(out, Field{field, tag, np}) - } - } - return out -} - -func File(pass *analysis.Pass, node lint.Positioner) *ast.File { - pass.Fset.PositionFor(node.Pos(), true) - m := pass.ResultOf[facts.TokenFile].(map[*token.File]*ast.File) - return m[pass.Fset.File(node.Pos())] -} - -// IsGenerated reports whether pos is in a generated file, It ignores -// //line directives. -func IsGenerated(pass *analysis.Pass, pos token.Pos) bool { - _, ok := Generator(pass, pos) - return ok -} - -// Generator returns the generator that generated the file containing -// pos. It ignores //line directives. -func Generator(pass *analysis.Pass, pos token.Pos) (facts.Generator, bool) { - file := pass.Fset.PositionFor(pos, false).Filename - m := pass.ResultOf[facts.Generated].(map[string]facts.Generator) - g, ok := m[file] - return g, ok -} - -func ReportfFG(pass *analysis.Pass, pos token.Pos, f string, args ...interface{}) { - file := lint.DisplayPosition(pass.Fset, pos).Filename - m := pass.ResultOf[facts.Generated].(map[string]facts.Generator) - if _, ok := m[file]; ok { - return - } - pass.Reportf(pos, f, args...) -} - -func ReportNodef(pass *analysis.Pass, node ast.Node, format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - pass.Report(analysis.Diagnostic{Pos: node.Pos(), End: node.End(), Message: msg}) -} - -func ReportNodefFG(pass *analysis.Pass, node ast.Node, format string, args ...interface{}) { - file := lint.DisplayPosition(pass.Fset, node.Pos()).Filename - m := pass.ResultOf[facts.Generated].(map[string]facts.Generator) - if _, ok := m[file]; ok { - return - } - ReportNodef(pass, node, format, args...) +// ExhaustiveTypeSwitch panics when called. It can be used to ensure +// that type switches are exhaustive. +func ExhaustiveTypeSwitch(v interface{}) { + panic(fmt.Sprintf("internal error: unhandled case %T", v)) } diff --git a/vendor/honnef.co/go/tools/lint/lintutil/format/format.go b/vendor/honnef.co/go/tools/lint/lintutil/format/format.go index 9385431f88..b28f8885b8 100644 --- a/vendor/honnef.co/go/tools/lint/lintutil/format/format.go +++ b/vendor/honnef.co/go/tools/lint/lintutil/format/format.go @@ -39,7 +39,7 @@ func relativePositionString(pos token.Position) string { } type Statter interface { - Stats(total, errors, warnings int) + Stats(total, errors, warnings, ignored int) } type Formatter interface { @@ -51,7 +51,10 @@ type Text struct { } func (o Text) Format(p lint.Problem) { - fmt.Fprintf(o.W, "%v: %s\n", relativePositionString(p.Pos), p.String()) + fmt.Fprintf(o.W, "%s: %s\n", relativePositionString(p.Pos), p.String()) + for _, r := range p.Related { + fmt.Fprintf(o.W, "\t%s: %s\n", relativePositionString(r.Pos), r.Message) + } } type JSON struct { @@ -76,12 +79,18 @@ func (o JSON) Format(p lint.Problem) { Line int `json:"line"` Column int `json:"column"` } - jp := struct { - Code string `json:"code"` - Severity string `json:"severity,omitempty"` + type related struct { Location location `json:"location"` End location `json:"end"` Message string `json:"message"` + } + jp := struct { + Code string `json:"code"` + Severity string `json:"severity,omitempty"` + Location location `json:"location"` + End location `json:"end"` + Message string `json:"message"` + Related []related `json:"related,omitempty"` }{ Code: p.Check, Severity: severity(p.Severity), @@ -97,6 +106,21 @@ func (o JSON) Format(p lint.Problem) { }, Message: p.Message, } + for _, r := range p.Related { + jp.Related = append(jp.Related, related{ + Location: location{ + File: r.Pos.Filename, + Line: r.Pos.Line, + Column: r.Pos.Column, + }, + End: location{ + File: r.End.Filename, + Line: r.End.Line, + Column: r.End.Column, + }, + Message: r.Message, + }) + } _ = json.NewEncoder(o.W).Encode(jp) } @@ -123,13 +147,16 @@ func (o *Stylish) Format(p lint.Problem) { o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0) } fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", pos.Line, pos.Column, p.Check, p.Message) + for _, r := range p.Related { + fmt.Fprintf(o.tw, " (%d, %d)\t\t %s\n", r.Pos.Line, r.Pos.Column, r.Message) + } } -func (o *Stylish) Stats(total, errors, warnings int) { +func (o *Stylish) Stats(total, errors, warnings, ignored int) { if o.tw != nil { o.tw.Flush() fmt.Fprintln(o.W) } - fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings)\n", - total, errors, warnings) + fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings, %d ignored)\n", + total, errors, warnings, ignored) } diff --git a/vendor/honnef.co/go/tools/lint/lintutil/util.go b/vendor/honnef.co/go/tools/lint/lintutil/util.go index fe0279f921..7c3dbdec19 100644 --- a/vendor/honnef.co/go/tools/lint/lintutil/util.go +++ b/vendor/honnef.co/go/tools/lint/lintutil/util.go @@ -23,7 +23,9 @@ import ( "runtime/pprof" "strconv" "strings" + "sync" "sync/atomic" + "time" "honnef.co/go/tools/config" "honnef.co/go/tools/internal/cache" @@ -114,6 +116,8 @@ func FlagSet(name string) *flag.FlagSet { flags.String("debug.memprofile", "", "Write memory profile to `file`") flags.Bool("debug.version", false, "Print detailed version information about this program") flags.Bool("debug.no-compile-errors", false, "Don't print compile errors") + flags.String("debug.measure-analyzers", "", "Write analysis measurements to `file`. `file` will be opened for appending if it already exists.") + flags.Uint("debug.repeat-analyzers", 0, "Run analyzers `num` times") checks := list{"inherit"} fail := list{"all"} @@ -153,6 +157,24 @@ func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs * memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string) debugVersion := fs.Lookup("debug.version").Value.(flag.Getter).Get().(bool) debugNoCompile := fs.Lookup("debug.no-compile-errors").Value.(flag.Getter).Get().(bool) + debugRepeat := fs.Lookup("debug.repeat-analyzers").Value.(flag.Getter).Get().(uint) + + var measureAnalyzers func(analysis *analysis.Analyzer, pkg *lint.Package, d time.Duration) + if path := fs.Lookup("debug.measure-analyzers").Value.(flag.Getter).Get().(string); path != "" { + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + log.Fatal(err) + } + + mu := &sync.Mutex{} + measureAnalyzers = func(analysis *analysis.Analyzer, pkg *lint.Package, d time.Duration) { + mu.Lock() + defer mu.Unlock() + if _, err := fmt.Fprintf(f, "%s\t%s\t%d\n", analysis.Name, pkg.ID, d.Nanoseconds()); err != nil { + log.Println("error writing analysis measurements:", err) + } + } + } cfg := config.Config{} cfg.Checks = *fs.Lookup("checks").Value.(*list) @@ -218,10 +240,12 @@ func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs * } ps, err := Lint(cs, cums, fs.Args(), &Options{ - Tags: tags, - LintTests: tests, - GoVersion: goVersion, - Config: cfg, + Tags: tags, + LintTests: tests, + GoVersion: goVersion, + Config: cfg, + PrintAnalyzerMeasurement: measureAnalyzers, + RepeatAnalyzers: debugRepeat, }) if err != nil { fmt.Fprintln(os.Stderr, err) @@ -245,6 +269,7 @@ func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs * total int errors int warnings int + ignored int ) fail := *fs.Lookup("fail").Value.(*list) @@ -262,6 +287,7 @@ func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs * continue } if p.Severity == lint.Ignored && !showIgnored { + ignored++ continue } if shouldExit[p.Check] { @@ -273,7 +299,7 @@ func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs * f.Format(p) } if f, ok := f.(format.Statter); ok { - f.Stats(total, errors, warnings) + f.Stats(total, errors, warnings, ignored) } if errors > 0 { exit(1) @@ -284,9 +310,11 @@ func ProcessFlagSet(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, fs * type Options struct { Config config.Config - Tags string - LintTests bool - GoVersion int + Tags string + LintTests bool + GoVersion int + PrintAnalyzerMeasurement func(analysis *analysis.Analyzer, pkg *lint.Package, d time.Duration) + RepeatAnalyzers uint } func computeSalt() ([]byte, error) { @@ -325,7 +353,9 @@ func Lint(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, paths []string CumulativeCheckers: cums, GoVersion: opt.GoVersion, Config: opt.Config, + RepeatAnalyzers: opt.RepeatAnalyzers, } + l.Stats.PrintAnalyzerMeasurement = opt.PrintAnalyzerMeasurement cfg := &packages.Config{} if opt.LintTests { cfg.Tests = true @@ -368,7 +398,8 @@ func Lint(cs []*analysis.Analyzer, cums []lint.CumulativeChecker, paths []string }() } - return l.Lint(cfg, paths) + ps, err := l.Lint(cfg, paths) + return ps, err } var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`) @@ -390,3 +421,24 @@ func parsePos(pos string) token.Position { Column: col, } } + +func InitializeAnalyzers(docs map[string]*lint.Documentation, analyzers map[string]*analysis.Analyzer) map[string]*analysis.Analyzer { + out := make(map[string]*analysis.Analyzer, len(analyzers)) + for k, v := range analyzers { + vc := *v + out[k] = &vc + + vc.Name = k + doc, ok := docs[k] + if !ok { + panic(fmt.Sprintf("missing documentation for check %s", k)) + } + vc.Doc = doc.String() + if vc.Flags.Usage == nil { + fs := flag.NewFlagSet("", flag.PanicOnError) + fs.Var(NewVersionFlag(), "go", "Target Go version") + vc.Flags = *fs + } + } + return out +} diff --git a/vendor/honnef.co/go/tools/lint/runner.go b/vendor/honnef.co/go/tools/lint/runner.go index 3b22a63fa2..74106ced82 100644 --- a/vendor/honnef.co/go/tools/lint/runner.go +++ b/vendor/honnef.co/go/tools/lint/runner.go @@ -1,6 +1,30 @@ package lint /* +Package loading + +Conceptually, package loading in the runner can be imagined as a +graph-shaped work list. We iteratively pop off leaf nodes (packages +that have no unloaded dependencies) and load data from export data, +our cache, or source. + +Specifically, non-initial packages are loaded from export data and the +fact cache if possible, otherwise from source. Initial packages are +loaded from export data, the fact cache and the (problems, ignores, +config) cache if possible, otherwise from source. + +The appeal of this approach is that it is both simple to implement and +easily parallelizable. Each leaf node can be processed independently, +and new leaf nodes appear as their dependencies are being processed. + +The downside of this approach, however, is that we're doing more work +than necessary. Imagine an initial package A, which has the following +dependency chain: A->B->C->D – in the current implementation, we will +load all 4 packages. However, if package A can be loaded fully from +cached information, then none of its dependencies are necessary, and +we could avoid loading them. + + Parallelism Runner implements parallel processing of packages by spawning one @@ -19,6 +43,34 @@ all execute in parallel, while not wasting resources for long linear chains or trying to process more subgraphs in parallel than the system can handle. + +Caching + +We make use of several caches. These caches are Go's export data, our +facts cache, and our (problems, ignores, config) cache. + +Initial packages will either be loaded from a combination of all three +caches, or from source. Non-initial packages will either be loaded +from a combination of export data and facts cache, or from source. + +The facts cache is separate from the (problems, ignores, config) cache +because when we process non-initial packages, we generate facts, but +we discard problems and ignores. + +The facts cache is keyed by (package, analyzer), whereas the +(problems, ignores, config) cache is keyed by (package, list of +analyzes). The difference between the two exists because there are +only a handful of analyses that produce facts, but hundreds of +analyses that don't. Creating one cache entry per fact-generating +analysis is feasible, creating one cache entry per normal analysis has +significant performance and storage overheads. + +The downside of keying by the list of analyzes is, naturally, that a +change in list of analyzes changes the cache key. `staticcheck -checks +A` and `staticcheck -checks A,B` will therefore need their own cache +entries and not reuse each other's work. This problem does not affect +the facts cache. + */ import ( @@ -37,6 +89,7 @@ import ( "strings" "sync" "sync/atomic" + "time" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" @@ -47,6 +100,11 @@ import ( "honnef.co/go/tools/loader" ) +func init() { + gob.Register(&FileIgnore{}) + gob.Register(&LineIgnore{}) +} + // If enabled, abuse of the go/analysis API will lead to panics const sanityCheck = true @@ -58,21 +116,43 @@ const sanityCheck = true // This may change unused's behavior, however, as it may observe fewer // interfaces from transitive dependencies. +// OPT(dh): every single package will have the same value for +// canClearTypes. We could move the Package.decUse method to runner to +// eliminate this field. This is probably not worth it, though. There +// are only thousands of packages, so the field only takes up +// kilobytes of memory. + +// OPT(dh): do we really need the Package.gen field? it's based +// trivially on pkg.results and merely caches the result of a type +// assertion. How often do we actually use the field? + type Package struct { + // dependents is initially set to 1 plus the number of packages + // that directly import this package. It is atomically decreased + // by 1 every time a dependent has been processed or when the + // package itself has been processed. Once the value reaches zero, + // the package is no longer needed. dependents uint64 *packages.Package - Imports []*Package - initial bool + Imports []*Package + initial bool + // fromSource is set to true for packages that have been loaded + // from source. This is the case for initial packages, packages + // with missing export data, and packages with no cached facts. fromSource bool - hash string - done chan struct{} + // hash stores the package hash, as computed by packageHash + hash string + actionID cache.ActionID + done chan struct{} resultsMu sync.Mutex - // results maps analyzer IDs to analyzer results + // results maps analyzer IDs to analyzer results. it is + // implemented as a deduplicating concurrent cache. results []*result - cfg *config.Config + cfg *config.Config + // gen maps file names to the code generator that created them gen map[string]facts.Generator problems []Problem ignores []Ignore @@ -82,12 +162,22 @@ type Package struct { facts []map[types.Object][]analysis.Fact pkgFacts [][]analysis.Fact + // canClearTypes is set to true if we can discard type + // information after the package and its dependents have been + // processed. This is the case when no cumulative checkers are + // being run. canClearTypes bool } +type cachedPackage struct { + Problems []Problem + Ignores []Ignore + Config *config.Config +} + func (pkg *Package) decUse() { - atomic.AddUint64(&pkg.dependents, ^uint64(0)) - if atomic.LoadUint64(&pkg.dependents) == 0 { + ret := atomic.AddUint64(&pkg.dependents, ^uint64(0)) + if ret == 0 { // nobody depends on this package anymore if pkg.canClearTypes { pkg.Types = nil @@ -108,16 +198,16 @@ type result struct { } type Runner struct { - ld loader.Loader - cache *cache.Cache + cache *cache.Cache + goVersion int + stats *Stats + repeatAnalyzers uint - analyzerIDs analyzerIDs + analyzerIDs analyzerIDs + problemsCacheKey string // limits parallelism of loading packages loadSem chan struct{} - - goVersion int - stats *Stats } type analyzerIDs struct { @@ -225,6 +315,13 @@ func (ac *analysisAction) report(pass *analysis.Pass, d analysis.Diagnostic) { Message: d.Message, Check: pass.Analyzer.Name, } + for _, r := range d.Related { + p.Related = append(p.Related, Related{ + Pos: DisplayPosition(pass.Fset, r.Pos), + End: DisplayPosition(pass.Fset, r.End), + Message: r.Message, + }) + } ac.problems = append(ac.problems, p) } @@ -278,6 +375,21 @@ func (r *Runner) runAnalysis(ac *analysisAction) (ret interface{}, err error) { } } +func (r *Runner) loadCachedPackage(pkg *Package, analyzers []*analysis.Analyzer) (cachedPackage, bool) { + // OPT(dh): we can cache this computation, it'll be the same for all packages + id := cache.Subkey(pkg.actionID, "data "+r.problemsCacheKey) + + b, _, err := r.cache.GetBytes(id) + if err != nil { + return cachedPackage{}, false + } + var cpkg cachedPackage + if err := gob.NewDecoder(bytes.NewReader(b)).Decode(&cpkg); err != nil { + return cachedPackage{}, false + } + return cpkg, true +} + func (r *Runner) loadCachedFacts(a *analysis.Analyzer, pkg *Package) ([]Fact, bool) { if len(a.FactTypes) == 0 { return nil, true @@ -285,10 +397,7 @@ func (r *Runner) loadCachedFacts(a *analysis.Analyzer, pkg *Package) ([]Fact, bo var facts []Fact // Look in the cache for facts - aID, err := passActionID(pkg, a) - if err != nil { - return nil, false - } + aID := passActionID(pkg, a) aID = cache.Subkey(aID, "facts") b, _, err := r.cache.GetBytes(aID) if err != nil { @@ -378,9 +487,15 @@ func (r *Runner) runAnalysisUser(pass *analysis.Pass, ac *analysisAction) (inter } // Then with this analyzer - ret, err := ac.analyzer.Run(pass) - if err != nil { - return nil, err + var ret interface{} + for i := uint(0); i < r.repeatAnalyzers+1; i++ { + var err error + t := time.Now() + ret, err = ac.analyzer.Run(pass) + r.stats.MeasureAnalyzer(ac.analyzer, ac.pkg, time.Since(t)) + if err != nil { + return nil, err + } } if len(ac.analyzer.FactTypes) > 0 { @@ -404,16 +519,7 @@ func (r *Runner) runAnalysisUser(pass *analysis.Pass, ac *analysisAction) (inter } } - buf := &bytes.Buffer{} - if err := gob.NewEncoder(buf).Encode(facts); err != nil { - return nil, err - } - aID, err := passActionID(ac.pkg, ac.analyzer) - if err != nil { - return nil, err - } - aID = cache.Subkey(aID, "facts") - if err := r.cache.PutBytes(aID, buf.Bytes()); err != nil { + if err := r.cacheData(facts, ac.pkg, ac.analyzer, "facts"); err != nil { return nil, err } } @@ -421,6 +527,19 @@ func (r *Runner) runAnalysisUser(pass *analysis.Pass, ac *analysisAction) (inter return ret, nil } +func (r *Runner) cacheData(v interface{}, pkg *Package, a *analysis.Analyzer, subkey string) error { + buf := &bytes.Buffer{} + if err := gob.NewEncoder(buf).Encode(v); err != nil { + return err + } + aID := passActionID(pkg, a) + aID = cache.Subkey(aID, subkey) + if err := r.cache.PutBytes(aID, buf.Bytes()); err != nil { + return err + } + return nil +} + func NewRunner(stats *Stats) (*Runner, error) { cache, err := cache.Default() if err != nil { @@ -438,9 +557,17 @@ func NewRunner(stats *Stats) (*Runner, error) { // diagnostics as well as extracted ignore directives. // // Note that diagnostics have not been filtered at this point yet, to -// accomodate cumulative analyzes that require additional steps to +// accommodate cumulative analyzes that require additional steps to // produce diagnostics. func (r *Runner) Run(cfg *packages.Config, patterns []string, analyzers []*analysis.Analyzer, hasCumulative bool) ([]*Package, error) { + checkerNames := make([]string, len(analyzers)) + for i, a := range analyzers { + checkerNames[i] = a.Name + } + sort.Strings(checkerNames) + r.problemsCacheKey = strings.Join(checkerNames, " ") + + var allAnalyzers []*analysis.Analyzer r.analyzerIDs = analyzerIDs{m: map[*analysis.Analyzer]int{}} id := 0 seen := map[*analysis.Analyzer]struct{}{} @@ -450,6 +577,7 @@ func (r *Runner) Run(cfg *packages.Config, patterns []string, analyzers []*analy return } seen[a] = struct{}{} + allAnalyzers = append(allAnalyzers, a) r.analyzerIDs.m[a] = id id++ for _, f := range a.FactTypes { @@ -468,6 +596,11 @@ func (r *Runner) Run(cfg *packages.Config, patterns []string, analyzers []*analy for _, a := range injectedAnalyses { dfs(a) } + // Run all analyzers on all packages (subject to further + // restrictions enforced later). This guarantees that if analyzer + // A1 depends on A2, and A2 has facts, that A2 will run on the + // dependencies of user-provided packages, even though A1 won't. + analyzers = allAnalyzers var dcfg packages.Config if cfg != nil { @@ -475,11 +608,10 @@ func (r *Runner) Run(cfg *packages.Config, patterns []string, analyzers []*analy } atomic.StoreUint32(&r.stats.State, StateGraph) - initialPkgs, err := r.ld.Graph(dcfg, patterns...) + initialPkgs, err := loader.Graph(dcfg, patterns...) if err != nil { return nil, err } - defer r.cache.Trim() var allPkgs []*Package @@ -507,7 +639,8 @@ func (r *Runner) Run(cfg *packages.Config, patterns []string, analyzers []*analy m[l].Imports = append(m[l].Imports, m[v]) } - m[l].hash, err = packageHash(m[l]) + m[l].hash, err = r.packageHash(m[l]) + m[l].actionID = packageActionID(m[l]) if err != nil { m[l].errs = append(m[l].errs, err) } @@ -564,27 +697,36 @@ func parsePos(pos string) (token.Position, int, error) { }, len(parts[0]), nil } -// loadPkg loads a Go package. If the package is in the set of initial -// packages, it will be loaded from source, otherwise it will be -// loaded from export data. In the case that the package was loaded -// from export data, cached facts will also be loaded. -// -// Currently, only cached facts for this package will be loaded, not -// for any of its dependencies. +// loadPkg loads a Go package. It may be loaded from a combination of +// caches, or from source. func (r *Runner) loadPkg(pkg *Package, analyzers []*analysis.Analyzer) error { if pkg.Types != nil { panic(fmt.Sprintf("internal error: %s has already been loaded", pkg.Package)) } - // Load type information if pkg.initial { - // Load package from source - pkg.fromSource = true - return r.ld.LoadFromSource(pkg.Package) + // Try to load cached package + cpkg, ok := r.loadCachedPackage(pkg, analyzers) + if ok { + pkg.problems = cpkg.Problems + pkg.ignores = cpkg.Ignores + pkg.cfg = cpkg.Config + } else { + pkg.fromSource = true + return loader.LoadFromSource(pkg.Package) + } } + // At this point we're either working with a non-initial package, + // or we managed to load cached problems for the package. We still + // need export data and facts. + + // OPT(dh): we don't need type information for this package if no + // other package depends on it. this may be the case for initial + // packages. + // Load package from export data - if err := r.ld.LoadFromExport(pkg.Package); err != nil { + if err := loader.LoadFromExport(pkg.Package); err != nil { // We asked Go to give us up to date export data, yet // we can't load it. There must be something wrong. // @@ -597,7 +739,7 @@ func (r *Runner) loadPkg(pkg *Package, analyzers []*analysis.Analyzer) error { // FIXME(dh): we no longer reload from export data, so // theoretically we should be able to continue pkg.fromSource = true - if err := r.ld.LoadFromSource(pkg.Package); err != nil { + if err := loader.LoadFromSource(pkg.Package); err != nil { return err } // Make sure this package can't be imported successfully @@ -658,13 +800,14 @@ func (r *Runner) loadPkg(pkg *Package, analyzers []*analysis.Analyzer) error { dfs(a) } - if failed { - pkg.fromSource = true - // XXX we added facts to the maps, we need to get rid of those - return r.ld.LoadFromSource(pkg.Package) + if !failed { + return nil } - return nil + // We failed to load some cached facts + pkg.fromSource = true + // XXX we added facts to the maps, we need to get rid of those + return loader.LoadFromSource(pkg.Package) } type analysisError struct { @@ -695,7 +838,7 @@ func (r *Runner) processPkg(pkg *Package, analyzers []*analysis.Analyzer) { }() // Ensure all packages have the generated map and config. This is - // required by interna of the runner. Analyses that themselves + // required by internals of the runner. Analyses that themselves // make use of either have an explicit dependency so that other // runners work correctly, too. analyzers = append(analyzers[0:len(analyzers):len(analyzers)], injectedAnalyses...) @@ -766,7 +909,7 @@ func (r *Runner) processPkg(pkg *Package, analyzers []*analysis.Analyzer) { defer wg.Done() // Only initial packages and packages with missing // facts will have been loaded from source. - if pkg.initial || r.hasFacts(a) { + if pkg.initial || len(a.FactTypes) > 0 { if _, err := r.runAnalysis(ac); err != nil { errs[i] = analysisError{a, pkg, err} return @@ -800,6 +943,8 @@ func (r *Runner) processPkg(pkg *Package, analyzers []*analysis.Analyzer) { // We can't process ignores at this point because `unused` needs // to see more than one package to make its decision. + // + // OPT(dh): can't we guard this block of code by pkg.initial? ignores, problems := parseDirectives(pkg.Package) pkg.ignores = append(pkg.ignores, ignores...) pkg.problems = append(pkg.problems, problems...) @@ -824,32 +969,6 @@ func (r *Runner) processPkg(pkg *Package, analyzers []*analysis.Analyzer) { // from processPkg. } -// hasFacts reports whether an analysis exports any facts. An analysis -// that has a transitive dependency that exports facts is considered -// to be exporting facts. -func (r *Runner) hasFacts(a *analysis.Analyzer) bool { - ret := false - seen := make([]bool, len(r.analyzerIDs.m)) - var dfs func(*analysis.Analyzer) - dfs = func(a *analysis.Analyzer) { - if seen[r.analyzerIDs.get(a)] { - return - } - seen[r.analyzerIDs.get(a)] = true - if len(a.FactTypes) > 0 { - ret = true - } - for _, req := range a.Requires { - if ret { - break - } - dfs(req) - } - } - dfs(a) - return ret -} - func parseDirective(s string) (cmd string, args []string) { if !strings.HasPrefix(s, "//lint:") { return "", nil @@ -912,7 +1031,7 @@ func parseDirectives(pkg *packages.Package) ([]Ignore, []Problem) { File: pos.Filename, Line: pos.Line, Checks: checks, - Pos: c.Pos(), + Pos: DisplayPosition(pkg.Fset, c.Pos()), } case "file-ignore": ig = &FileIgnore{ @@ -932,9 +1051,10 @@ func parseDirectives(pkg *packages.Package) ([]Ignore, []Problem) { // packageHash computes a package's hash. The hash is based on all Go // files that make up the package, as well as the hashes of imported // packages. -func packageHash(pkg *Package) (string, error) { +func (r *Runner) packageHash(pkg *Package) (string, error) { key := cache.NewHash("package hash") fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath) + fmt.Fprintf(key, "go %d\n", r.goVersion) for _, f := range pkg.CompiledGoFiles { h, err := cache.FileHash(f) if err != nil { @@ -943,6 +1063,28 @@ func packageHash(pkg *Package) (string, error) { fmt.Fprintf(key, "file %s %x\n", f, h) } + // Actually load the configuration to calculate its hash. This + // will take into consideration inheritance of configuration + // files, as well as the default configuration. + // + // OPT(dh): doing this means we'll load the config twice: once for + // computing the hash, and once when analyzing the package from + // source. + cdir := config.Dir(pkg.GoFiles) + if cdir == "" { + fmt.Fprintf(key, "file %s %x\n", config.ConfigName, [cache.HashSize]byte{}) + } else { + cfg, err := config.Load(cdir) + if err != nil { + return "", err + } + h := cache.NewHash(config.ConfigName) + if _, err := h.Write([]byte(cfg.String())); err != nil { + return "", err + } + fmt.Fprintf(key, "file %s %x\n", config.ConfigName, h.Sum()) + } + imps := make([]*Package, len(pkg.Imports)) copy(imps, pkg.Imports) sort.Slice(imps, func(i, j int) bool { @@ -959,12 +1101,14 @@ func packageHash(pkg *Package) (string, error) { return hex.EncodeToString(h[:]), nil } -// passActionID computes an ActionID for an analysis pass. -func passActionID(pkg *Package, analyzer *analysis.Analyzer) (cache.ActionID, error) { - key := cache.NewHash("action ID") +func packageActionID(pkg *Package) cache.ActionID { + key := cache.NewHash("package ID") fmt.Fprintf(key, "pkgpath %s\n", pkg.PkgPath) fmt.Fprintf(key, "pkghash %s\n", pkg.hash) - fmt.Fprintf(key, "analyzer %s\n", analyzer.Name) + return key.Sum() +} - return key.Sum(), nil +// passActionID computes an ActionID for an analysis pass. +func passActionID(pkg *Package, analyzer *analysis.Analyzer) cache.ActionID { + return cache.Subkey(pkg.actionID, fmt.Sprintf("analyzer %s", analyzer.Name)) } diff --git a/vendor/honnef.co/go/tools/lint/stats.go b/vendor/honnef.co/go/tools/lint/stats.go index 2f65085593..85eb978448 100644 --- a/vendor/honnef.co/go/tools/lint/stats.go +++ b/vendor/honnef.co/go/tools/lint/stats.go @@ -1,5 +1,11 @@ package lint +import ( + "time" + + "golang.org/x/tools/go/analysis" +) + const ( StateInitializing = 0 StateGraph = 1 @@ -17,4 +23,16 @@ type Stats struct { Problems uint32 ActiveWorkers uint32 TotalWorkers uint32 + PrintAnalyzerMeasurement func(*analysis.Analyzer, *Package, time.Duration) +} + +type AnalysisMeasurementKey struct { + Analysis string + Pkg string +} + +func (s *Stats) MeasureAnalyzer(analysis *analysis.Analyzer, pkg *Package, d time.Duration) { + if s.PrintAnalyzerMeasurement != nil { + s.PrintAnalyzerMeasurement(analysis, pkg, d) + } } diff --git a/vendor/honnef.co/go/tools/loader/loader.go b/vendor/honnef.co/go/tools/loader/loader.go index 9c6885d485..a14f274d29 100644 --- a/vendor/honnef.co/go/tools/loader/loader.go +++ b/vendor/honnef.co/go/tools/loader/loader.go @@ -1,6 +1,7 @@ package loader import ( + "errors" "fmt" "go/ast" "go/parser" @@ -9,22 +10,17 @@ import ( "go/types" "log" "os" - "sync" "golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/go/packages" ) -type Loader struct { - exportMu sync.RWMutex -} - // Graph resolves patterns and returns packages with all the // information required to later load type information, and optionally // syntax trees. // // The provided config can set any setting with the exception of Mode. -func (ld *Loader) Graph(cfg packages.Config, patterns ...string) ([]*packages.Package, error) { +func Graph(cfg packages.Config, patterns ...string) ([]*packages.Package, error) { cfg.Mode = packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedTypesSizes pkgs, err := packages.Load(&cfg, patterns...) if err != nil { @@ -34,15 +30,29 @@ func (ld *Loader) Graph(cfg packages.Config, patterns ...string) ([]*packages.Pa packages.Visit(pkgs, nil, func(pkg *packages.Package) { pkg.Fset = fset }) - return pkgs, nil + + n := 0 + for _, pkg := range pkgs { + if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" { + // If a package consists only of test files, then + // go/packages incorrectly(?) returns an empty package for + // the non-test variant. Get rid of those packages. See + // #646. + // + // Do not, however, skip packages that have errors. Those, + // too, may have no files, but we want to print the + // errors. + continue + } + pkgs[n] = pkg + n++ + } + return pkgs[:n], nil } // LoadFromExport loads a package from export data. All of its // dependencies must have been loaded already. -func (ld *Loader) LoadFromExport(pkg *packages.Package) error { - ld.exportMu.Lock() - defer ld.exportMu.Unlock() - +func LoadFromExport(pkg *packages.Package) error { pkg.IllTyped = true for path, pkg := range pkg.Imports { if pkg.Types == nil { @@ -87,10 +97,7 @@ func (ld *Loader) LoadFromExport(pkg *packages.Package) error { // LoadFromSource loads a package from source. All of its dependencies // must have been loaded already. -func (ld *Loader) LoadFromSource(pkg *packages.Package) error { - ld.exportMu.RLock() - defer ld.exportMu.RUnlock() - +func LoadFromSource(pkg *packages.Package) error { pkg.IllTyped = true pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name) @@ -121,6 +128,12 @@ func (ld *Loader) LoadFromSource(pkg *packages.Package) error { if path == "unsafe" { return types.Unsafe, nil } + if path == "C" { + // go/packages doesn't tell us that cgo preprocessing + // failed. When we subsequently try to parse the package, + // we'll encounter the raw C import. + return nil, errors.New("cgo preprocessing failed") + } imp := pkg.Imports[path] if imp == nil { return nil, nil diff --git a/vendor/honnef.co/go/tools/pattern/convert.go b/vendor/honnef.co/go/tools/pattern/convert.go new file mode 100644 index 0000000000..dfcd1560d7 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/convert.go @@ -0,0 +1,242 @@ +package pattern + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" +) + +var astTypes = map[string]reflect.Type{ + "Ellipsis": reflect.TypeOf(ast.Ellipsis{}), + "RangeStmt": reflect.TypeOf(ast.RangeStmt{}), + "AssignStmt": reflect.TypeOf(ast.AssignStmt{}), + "IndexExpr": reflect.TypeOf(ast.IndexExpr{}), + "Ident": reflect.TypeOf(ast.Ident{}), + "ValueSpec": reflect.TypeOf(ast.ValueSpec{}), + "GenDecl": reflect.TypeOf(ast.GenDecl{}), + "BinaryExpr": reflect.TypeOf(ast.BinaryExpr{}), + "ForStmt": reflect.TypeOf(ast.ForStmt{}), + "ArrayType": reflect.TypeOf(ast.ArrayType{}), + "DeferStmt": reflect.TypeOf(ast.DeferStmt{}), + "MapType": reflect.TypeOf(ast.MapType{}), + "ReturnStmt": reflect.TypeOf(ast.ReturnStmt{}), + "SliceExpr": reflect.TypeOf(ast.SliceExpr{}), + "StarExpr": reflect.TypeOf(ast.StarExpr{}), + "UnaryExpr": reflect.TypeOf(ast.UnaryExpr{}), + "SendStmt": reflect.TypeOf(ast.SendStmt{}), + "SelectStmt": reflect.TypeOf(ast.SelectStmt{}), + "ImportSpec": reflect.TypeOf(ast.ImportSpec{}), + "IfStmt": reflect.TypeOf(ast.IfStmt{}), + "GoStmt": reflect.TypeOf(ast.GoStmt{}), + "Field": reflect.TypeOf(ast.Field{}), + "SelectorExpr": reflect.TypeOf(ast.SelectorExpr{}), + "StructType": reflect.TypeOf(ast.StructType{}), + "KeyValueExpr": reflect.TypeOf(ast.KeyValueExpr{}), + "FuncType": reflect.TypeOf(ast.FuncType{}), + "FuncLit": reflect.TypeOf(ast.FuncLit{}), + "FuncDecl": reflect.TypeOf(ast.FuncDecl{}), + "ChanType": reflect.TypeOf(ast.ChanType{}), + "CallExpr": reflect.TypeOf(ast.CallExpr{}), + "CaseClause": reflect.TypeOf(ast.CaseClause{}), + "CommClause": reflect.TypeOf(ast.CommClause{}), + "CompositeLit": reflect.TypeOf(ast.CompositeLit{}), + "EmptyStmt": reflect.TypeOf(ast.EmptyStmt{}), + "SwitchStmt": reflect.TypeOf(ast.SwitchStmt{}), + "TypeSwitchStmt": reflect.TypeOf(ast.TypeSwitchStmt{}), + "TypeAssertExpr": reflect.TypeOf(ast.TypeAssertExpr{}), + "TypeSpec": reflect.TypeOf(ast.TypeSpec{}), + "InterfaceType": reflect.TypeOf(ast.InterfaceType{}), + "BranchStmt": reflect.TypeOf(ast.BranchStmt{}), + "IncDecStmt": reflect.TypeOf(ast.IncDecStmt{}), + "BasicLit": reflect.TypeOf(ast.BasicLit{}), +} + +func ASTToNode(node interface{}) Node { + switch node := node.(type) { + case *ast.File: + panic("cannot convert *ast.File to Node") + case nil: + return Nil{} + case string: + return String(node) + case token.Token: + return Token(node) + case *ast.ExprStmt: + return ASTToNode(node.X) + case *ast.BlockStmt: + if node == nil { + return Nil{} + } + return ASTToNode(node.List) + case *ast.FieldList: + if node == nil { + return Nil{} + } + return ASTToNode(node.List) + case *ast.BasicLit: + if node == nil { + return Nil{} + } + case *ast.ParenExpr: + return ASTToNode(node.X) + } + + if node, ok := node.(ast.Node); ok { + name := reflect.TypeOf(node).Elem().Name() + T, ok := structNodes[name] + if !ok { + panic(fmt.Sprintf("internal error: unhandled type %T", node)) + } + + if reflect.ValueOf(node).IsNil() { + return Nil{} + } + v := reflect.ValueOf(node).Elem() + objs := make([]Node, T.NumField()) + for i := 0; i < T.NumField(); i++ { + f := v.FieldByName(T.Field(i).Name) + objs[i] = ASTToNode(f.Interface()) + } + + n, err := populateNode(name, objs, false) + if err != nil { + panic(fmt.Sprintf("internal error: %s", err)) + } + return n + } + + s := reflect.ValueOf(node) + if s.Kind() == reflect.Slice { + if s.Len() == 0 { + return List{} + } + if s.Len() == 1 { + return ASTToNode(s.Index(0).Interface()) + } + + tail := List{} + for i := s.Len() - 1; i >= 0; i-- { + head := ASTToNode(s.Index(i).Interface()) + l := List{ + Head: head, + Tail: tail, + } + tail = l + } + return tail + } + + panic(fmt.Sprintf("internal error: unhandled type %T", node)) +} + +func NodeToAST(node Node, state State) interface{} { + switch node := node.(type) { + case Binding: + v, ok := state[node.Name] + if !ok { + // really we want to return an error here + panic("XXX") + } + switch v := v.(type) { + case types.Object: + return &ast.Ident{Name: v.Name()} + default: + return v + } + case Builtin, Any, Object, Function, Not, Or: + panic("XXX") + case List: + if (node == List{}) { + return []ast.Node{} + } + x := []ast.Node{NodeToAST(node.Head, state).(ast.Node)} + x = append(x, NodeToAST(node.Tail, state).([]ast.Node)...) + return x + case Token: + return token.Token(node) + case String: + return string(node) + case Nil: + return nil + } + + name := reflect.TypeOf(node).Name() + T, ok := astTypes[name] + if !ok { + panic(fmt.Sprintf("internal error: unhandled type %T", node)) + } + v := reflect.ValueOf(node) + out := reflect.New(T) + for i := 0; i < T.NumField(); i++ { + fNode := v.FieldByName(T.Field(i).Name) + if (fNode == reflect.Value{}) { + continue + } + fAST := out.Elem().FieldByName(T.Field(i).Name) + switch fAST.Type().Kind() { + case reflect.Slice: + c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state)) + if c.Kind() != reflect.Slice { + // it's a single node in the pattern, we have to wrap + // it in a slice + slice := reflect.MakeSlice(fAST.Type(), 1, 1) + slice.Index(0).Set(c) + c = slice + } + switch fAST.Interface().(type) { + case []ast.Node: + switch cc := c.Interface().(type) { + case []ast.Node: + fAST.Set(c) + case []ast.Expr: + var slice []ast.Node + for _, el := range cc { + slice = append(slice, el) + } + fAST.Set(reflect.ValueOf(slice)) + default: + panic("XXX") + } + case []ast.Expr: + switch cc := c.Interface().(type) { + case []ast.Node: + var slice []ast.Expr + for _, el := range cc { + slice = append(slice, el.(ast.Expr)) + } + fAST.Set(reflect.ValueOf(slice)) + case []ast.Expr: + fAST.Set(c) + default: + panic("XXX") + } + default: + panic("XXX") + } + case reflect.Int: + c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state)) + switch c.Kind() { + case reflect.String: + tok, ok := tokensByString[c.Interface().(string)] + if !ok { + // really we want to return an error here + panic("XXX") + } + fAST.SetInt(int64(tok)) + case reflect.Int: + fAST.Set(c) + default: + panic(fmt.Sprintf("internal error: unexpected kind %s", c.Kind())) + } + default: + r := NodeToAST(fNode.Interface().(Node), state) + if r != nil { + fAST.Set(reflect.ValueOf(r)) + } + } + } + + return out.Interface().(ast.Node) +} diff --git a/vendor/honnef.co/go/tools/pattern/doc.go b/vendor/honnef.co/go/tools/pattern/doc.go new file mode 100644 index 0000000000..05d86c2514 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/doc.go @@ -0,0 +1,273 @@ +/* +Package pattern implements a simple language for pattern matching Go ASTs. + +Design decisions and trade-offs + +The language is designed specifically for the task of filtering ASTs +to simplify the implementation of analyses in staticcheck. +It is also intended to be trivial to parse and execute. + +To that end, we make certain decisions that make the language more +suited to its task, while making certain queries infeasible. + +Furthermore, it is fully expected that the majority of analyses will still require ordinary Go code +to further process the filtered AST, to make use of type information and to enforce complex invariants. +It is not our goal to design a scripting language for writing entire checks in. + +The language + +At its core, patterns are a representation of Go ASTs, allowing for the use of placeholders to enable pattern matching. +Their syntax is inspired by LISP and Haskell, but unlike LISP, the core unit of patterns isn't the list, but the node. +There is a fixed set of nodes, identified by name, and with the exception of the Or node, all nodes have a fixed number of arguments. +In addition to nodes, there are atoms, which represent basic units such as strings or the nil value. + +Pattern matching is implemented via bindings, represented by the Binding node. +A Binding can match nodes and associate them with names, to later recall the nodes. +This allows for expressing "this node must be equal to that node" constraints. + +To simplify writing and reading patterns, a small amount of additional syntax exists on top of nodes and atoms. +This additional syntax doesn't add any new features of its own, it simply provides shortcuts to creating nodes and atoms. + +To show an example of a pattern, first consider this snippet of Go code: + + if x := fn(); x != nil { + for _, v := range x { + println(v, x) + } + } + +The corresponding AST expressed as an idiomatic pattern would look as follows: + + (IfStmt + (AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") [])) + (BinaryExpr (Ident "x") "!=" (Ident "nil")) + (RangeStmt + (Ident "_") (Ident "v") ":=" (Ident "x") + (CallExpr (Ident "println") [(Ident "v") (Ident "x")])) + nil) + +Two things are worth noting about this representation. +First, the [el1 el2 ...] syntax is a short-hand for creating lists. +It is a short-hand for el1:el2:[], which itself is a short-hand for (List el1 (List el2 (List nil nil)). +Second, note the absence of a lot of lists in places that normally accept lists. +For example, assignment assigns a number of right-hands to a number of left-hands, yet our AssignStmt is lacking any form of list. +This is due to the fact that a single node can match a list of exactly one element. +Thus, the two following forms have identical matching behavior: + + (AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") [])) + (AssignStmt [(Ident "x")] ":=" [(CallExpr (Ident "fn") [])]) + +This section serves as an overview of the language's syntax. +More in-depth explanations of the matching behavior as well as an exhaustive list of node types follows in the coming sections. + +Pattern matching + +TODO write about pattern matching + +- inspired by haskell syntax, but much, much simpler and naive + +Node types + +The language contains two kinds of nodes: those that map to nodes in the AST, and those that implement additional logic. + +Nodes that map directly to AST nodes are named identically to the types in the go/ast package. +What follows is an exhaustive list of these nodes: + + (ArrayType len elt) + (AssignStmt lhs tok rhs) + (BasicLit kind value) + (BinaryExpr x op y) + (BranchStmt tok label) + (CallExpr fun args) + (CaseClause list body) + (ChanType dir value) + (CommClause comm body) + (CompositeLit type elts) + (DeferStmt call) + (Ellipsis elt) + (EmptyStmt) + (Field names type tag) + (ForStmt init cond post body) + (FuncDecl recv name type body) + (FuncLit type body) + (FuncType params results) + (GenDecl specs) + (GoStmt call) + (Ident name) + (IfStmt init cond body else) + (ImportSpec name path) + (IncDecStmt x tok) + (IndexExpr x index) + (InterfaceType methods) + (KeyValueExpr key value) + (MapType key value) + (RangeStmt key value tok x body) + (ReturnStmt results) + (SelectStmt body) + (SelectorExpr x sel) + (SendStmt chan value) + (SliceExpr x low high max) + (StarExpr x) + (StructType fields) + (SwitchStmt init tag body) + (TypeAssertExpr) + (TypeSpec name type) + (TypeSwitchStmt init assign body) + (UnaryExpr op x) + (ValueSpec names type values) + +Additionally, there are the String, Token and nil atoms. +Strings are double-quoted string literals, as in (Ident "someName"). +Tokens are also represented as double-quoted string literals, but are converted to token.Token values in contexts that require tokens, +such as in (BinaryExpr x "<" y), where "<" is transparently converted to token.LSS during matching. +The keyword 'nil' denotes the nil value, which represents the absence of any value. + +We also defines the (List head tail) node, which is used to represent sequences of elements as a singly linked list. +The head is a single element, and the tail is the remainder of the list. +For example, + + (List "foo" (List "bar" (List "baz" (List nil nil)))) + +represents a list of three elements, "foo", "bar" and "baz". There is dedicated syntax for writing lists, which looks as follows: + + ["foo" "bar" "baz"] + +This syntax is itself syntactic sugar for the following form: + + "foo":"bar":"baz":[] + +This form is of particular interest for pattern matching, as it allows matching on the head and tail. For example, + + "foo":"bar":_ + +would match any list with at least two elements, where the first two elements are "foo" and "bar". This is equivalent to writing + + (List "foo" (List "bar" _)) + +Note that it is not possible to match from the end of the list. +That is, there is no way to express a query such as "a list of any length where the last element is foo". + +Note that unlike in LISP, nil and empty lists are distinct from one another. +In patterns, with respect to lists, nil is akin to Go's untyped nil. +It will match a nil ast.Node, but it will not match a nil []ast.Expr. Nil will, however, match pointers to named types such as *ast.Ident. +Similarly, lists are akin to Go's +slices. An empty list will match both a nil and an empty []ast.Expr, but it will not match a nil ast.Node. + +Due to the difference between nil and empty lists, an empty list is represented as (List nil nil), i.e. a list with no head or tail. +Similarly, a list of one element is represented as (List el (List nil nil)). Unlike in LISP, it cannot be represented by (List el nil). + +Finally, there are nodes that implement special logic or matching behavior. + +(Any) matches any value. The underscore (_) maps to this node, making the following two forms equivalent: + + (Ident _) + (Ident (Any)) + +(Builtin name) matches a built-in identifier or function by name. +This is a type-aware variant of (Ident name). +Instead of only comparing the name, it resolves the object behind the name and makes sure it's a pre-declared identifier. + +For example, in the following piece of code + + func fn() { + println(true) + true := false + println(true) + } + +the pattern + + (Builtin "true") + +will match exactly once, on the first use of 'true' in the function. +Subsequent occurrences of 'true' no longer refer to the pre-declared identifier. + +(Object name) matches an identifier by name, but yields the +types.Object it refers to. + +(Function name) matches ast.Idents and ast.SelectorExprs that refer to a function with a given fully qualified name. +For example, "net/url.PathEscape" matches the PathEscape function in the net/url package, +and "(net/url.EscapeError).Error" refers to the Error method on the net/url.EscapeError type, +either on an instance of the type, or on the type itself. + +For example, the following patterns match the following lines of code: + + (CallExpr (Function "fmt.Println") _) // pattern 1 + (CallExpr (Function "(net/url.EscapeError).Error") _) // pattern 2 + + fmt.Println("hello, world") // matches pattern 1 + var x url.EscapeError + x.Error() // matches pattern 2 + (url.EscapeError).Error(x) // also matches pattern 2 + +(Binding name node) creates or uses a binding. +Bindings work like variable assignments, allowing referring to already matched nodes. +As an example, bindings are necessary to match self-assignment of the form "x = x", +since we need to express that the right-hand side is identical to the left-hand side. + +If a binding's node is not nil, the matcher will attempt to match a node according to the pattern. +If a binding's node is nil, the binding will either recall an existing value, or match the Any node. +It is an error to provide a non-nil node to a binding that has already been bound. + +Referring back to the earlier example, the following pattern will match self-assignment of idents: + + (AssignStmt (Binding "lhs" (Ident _)) "=" (Binding "lhs" nil)) + +Because bindings are a crucial component of pattern matching, there is special syntax for creating and recalling bindings. +Lower-case names refer to bindings. If standing on its own, the name "foo" will be equivalent to (Binding "foo" nil). +If a name is followed by an at-sign (@) then it will create a binding for the node that follows. +Together, this allows us to rewrite the earlier example as follows: + + (AssignStmt lhs@(Ident _) "=" lhs) + +(Or nodes...) is a variadic node that tries matching each node until one succeeds. For example, the following pattern matches all idents of name "foo" or "bar": + + (Ident (Or "foo" "bar")) + +We could also have written + + (Or (Ident "foo") (Ident "bar")) + +and achieved the same result. We can also mix different kinds of nodes: + + (Or (Ident "foo") (CallExpr (Ident "bar") _)) + +When using bindings inside of nodes used inside Or, all or none of the bindings will be bound. +That is, partially matched nodes that ultimately failed to match will not produce any bindings observable outside of the matching attempt. +We can thus write + + (Or (Ident name) (CallExpr name)) + +and 'name' will either be a String if the first option matched, or an Ident or SelectorExpr if the second option matched. + +(Not node) + +The Not node negates a match. For example, (Not (Ident _)) will match all nodes that aren't identifiers. + +ChanDir(0) + +Automatic unnesting of AST nodes + +The Go AST has several types of nodes that wrap other nodes. +To simplify matching, we automatically unwrap some of these nodes. + +These nodes are ExprStmt (for using expressions in a statement context), +ParenExpr (for parenthesized expressions), +DeclStmt (for declarations in a statement context), +and LabeledStmt (for labeled statements). + +Thus, the query + + (FuncLit _ [(CallExpr _ _)] + +will match a function literal containing a single function call, +even though in the actual Go AST, the CallExpr is nested inside an ExprStmt, +as function bodies are made up of sequences of statements. + +On the flip-side, there is no way to specifically match these wrapper nodes. +For example, there is no way of searching for unnecessary parentheses, like in the following piece of Go code: + + ((x)) += 2 + +*/ +package pattern diff --git a/vendor/honnef.co/go/tools/pattern/fuzz.go b/vendor/honnef.co/go/tools/pattern/fuzz.go new file mode 100644 index 0000000000..52e7df9742 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/fuzz.go @@ -0,0 +1,50 @@ +// +build gofuzz + +package pattern + +import ( + "go/ast" + goparser "go/parser" + "go/token" + "os" + "path/filepath" + "strings" +) + +var files []*ast.File + +func init() { + fset := token.NewFileSet() + filepath.Walk("/usr/lib/go/src", func(path string, info os.FileInfo, err error) error { + if err != nil { + // XXX error handling + panic(err) + } + if !strings.HasSuffix(path, ".go") { + return nil + } + f, err := goparser.ParseFile(fset, path, nil, 0) + if err != nil { + return nil + } + files = append(files, f) + return nil + }) +} + +func Fuzz(data []byte) int { + p := &Parser{} + pat, err := p.Parse(string(data)) + if err != nil { + if strings.Contains(err.Error(), "internal error") { + panic(err) + } + return 0 + } + _ = pat.Root.String() + + for _, f := range files { + Match(pat.Root, f) + } + return 1 +} diff --git a/vendor/honnef.co/go/tools/pattern/lexer.go b/vendor/honnef.co/go/tools/pattern/lexer.go new file mode 100644 index 0000000000..fb72e392bd --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/lexer.go @@ -0,0 +1,221 @@ +package pattern + +import ( + "fmt" + "go/token" + "unicode" + "unicode/utf8" +) + +type lexer struct { + f *token.File + + input string + start int + pos int + width int + items chan item +} + +type itemType int + +const eof = -1 + +const ( + itemError itemType = iota + itemLeftParen + itemRightParen + itemLeftBracket + itemRightBracket + itemTypeName + itemVariable + itemAt + itemColon + itemBlank + itemString + itemEOF +) + +func (typ itemType) String() string { + switch typ { + case itemError: + return "ERROR" + case itemLeftParen: + return "(" + case itemRightParen: + return ")" + case itemLeftBracket: + return "[" + case itemRightBracket: + return "]" + case itemTypeName: + return "TYPE" + case itemVariable: + return "VAR" + case itemAt: + return "@" + case itemColon: + return ":" + case itemBlank: + return "_" + case itemString: + return "STRING" + case itemEOF: + return "EOF" + default: + return fmt.Sprintf("itemType(%d)", typ) + } +} + +type item struct { + typ itemType + val string + pos int +} + +type stateFn func(*lexer) stateFn + +func (l *lexer) run() { + for state := lexStart; state != nil; { + state = state(l) + } + close(l.items) +} + +func (l *lexer) emitValue(t itemType, value string) { + l.items <- item{t, value, l.start} + l.start = l.pos +} + +func (l *lexer) emit(t itemType) { + l.items <- item{t, l.input[l.start:l.pos], l.start} + l.start = l.pos +} + +func lexStart(l *lexer) stateFn { + switch r := l.next(); { + case r == eof: + l.emit(itemEOF) + return nil + case unicode.IsSpace(r): + l.ignore() + case r == '(': + l.emit(itemLeftParen) + case r == ')': + l.emit(itemRightParen) + case r == '[': + l.emit(itemLeftBracket) + case r == ']': + l.emit(itemRightBracket) + case r == '@': + l.emit(itemAt) + case r == ':': + l.emit(itemColon) + case r == '_': + l.emit(itemBlank) + case r == '"': + l.backup() + return lexString + case unicode.IsUpper(r): + l.backup() + return lexType + case unicode.IsLower(r): + l.backup() + return lexVariable + default: + return l.errorf("unexpected character %c", r) + } + return lexStart +} + +func (l *lexer) next() (r rune) { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) + + if r == '\n' { + l.f.AddLine(l.pos) + } + + l.pos += l.width + + return r +} + +func (l *lexer) ignore() { + l.start = l.pos +} + +func (l *lexer) backup() { + l.pos -= l.width +} + +func (l *lexer) errorf(format string, args ...interface{}) stateFn { + // TODO(dh): emit position information in errors + l.items <- item{ + itemError, + fmt.Sprintf(format, args...), + l.start, + } + return nil +} + +func isAlphaNumeric(r rune) bool { + return r >= '0' && r <= '9' || + r >= 'a' && r <= 'z' || + r >= 'A' && r <= 'Z' +} + +func lexString(l *lexer) stateFn { + l.next() // skip quote + escape := false + + var runes []rune + for { + switch r := l.next(); r { + case eof: + return l.errorf("unterminated string") + case '"': + if !escape { + l.emitValue(itemString, string(runes)) + return lexStart + } else { + runes = append(runes, '"') + escape = false + } + case '\\': + if escape { + runes = append(runes, '\\') + escape = false + } else { + escape = true + } + default: + runes = append(runes, r) + } + } +} + +func lexType(l *lexer) stateFn { + l.next() + for { + if !isAlphaNumeric(l.next()) { + l.backup() + l.emit(itemTypeName) + return lexStart + } + } +} + +func lexVariable(l *lexer) stateFn { + l.next() + for { + if !isAlphaNumeric(l.next()) { + l.backup() + l.emit(itemVariable) + return lexStart + } + } +} diff --git a/vendor/honnef.co/go/tools/pattern/match.go b/vendor/honnef.co/go/tools/pattern/match.go new file mode 100644 index 0000000000..ff039baa75 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/match.go @@ -0,0 +1,513 @@ +package pattern + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + + "honnef.co/go/tools/lint" +) + +var tokensByString = map[string]Token{ + "INT": Token(token.INT), + "FLOAT": Token(token.FLOAT), + "IMAG": Token(token.IMAG), + "CHAR": Token(token.CHAR), + "STRING": Token(token.STRING), + "+": Token(token.ADD), + "-": Token(token.SUB), + "*": Token(token.MUL), + "/": Token(token.QUO), + "%": Token(token.REM), + "&": Token(token.AND), + "|": Token(token.OR), + "^": Token(token.XOR), + "<<": Token(token.SHL), + ">>": Token(token.SHR), + "&^": Token(token.AND_NOT), + "+=": Token(token.ADD_ASSIGN), + "-=": Token(token.SUB_ASSIGN), + "*=": Token(token.MUL_ASSIGN), + "/=": Token(token.QUO_ASSIGN), + "%=": Token(token.REM_ASSIGN), + "&=": Token(token.AND_ASSIGN), + "|=": Token(token.OR_ASSIGN), + "^=": Token(token.XOR_ASSIGN), + "<<=": Token(token.SHL_ASSIGN), + ">>=": Token(token.SHR_ASSIGN), + "&^=": Token(token.AND_NOT_ASSIGN), + "&&": Token(token.LAND), + "||": Token(token.LOR), + "<-": Token(token.ARROW), + "++": Token(token.INC), + "--": Token(token.DEC), + "==": Token(token.EQL), + "<": Token(token.LSS), + ">": Token(token.GTR), + "=": Token(token.ASSIGN), + "!": Token(token.NOT), + "!=": Token(token.NEQ), + "<=": Token(token.LEQ), + ">=": Token(token.GEQ), + ":=": Token(token.DEFINE), + "...": Token(token.ELLIPSIS), + "IMPORT": Token(token.IMPORT), + "VAR": Token(token.VAR), + "TYPE": Token(token.TYPE), + "CONST": Token(token.CONST), +} + +func maybeToken(node Node) (Node, bool) { + if node, ok := node.(String); ok { + if tok, ok := tokensByString[string(node)]; ok { + return tok, true + } + return node, false + } + return node, false +} + +func isNil(v interface{}) bool { + if v == nil { + return true + } + if _, ok := v.(Nil); ok { + return true + } + return false +} + +type matcher interface { + Match(*Matcher, interface{}) (interface{}, bool) +} + +type State = map[string]interface{} + +type Matcher struct { + TypesInfo *types.Info + State State +} + +func (m *Matcher) fork() *Matcher { + state := make(State, len(m.State)) + for k, v := range m.State { + state[k] = v + } + return &Matcher{ + TypesInfo: m.TypesInfo, + State: state, + } +} + +func (m *Matcher) merge(mc *Matcher) { + m.State = mc.State +} + +func (m *Matcher) Match(a Node, b ast.Node) bool { + m.State = State{} + _, ok := match(m, a, b) + return ok +} + +func Match(a Node, b ast.Node) (*Matcher, bool) { + m := &Matcher{} + ret := m.Match(a, b) + return m, ret +} + +// Match two items, which may be (Node, AST) or (AST, AST) +func match(m *Matcher, l, r interface{}) (interface{}, bool) { + if _, ok := r.(Node); ok { + panic("Node mustn't be on right side of match") + } + + switch l := l.(type) { + case *ast.ParenExpr: + return match(m, l.X, r) + case *ast.ExprStmt: + return match(m, l.X, r) + case *ast.DeclStmt: + return match(m, l.Decl, r) + case *ast.LabeledStmt: + return match(m, l.Stmt, r) + case *ast.BlockStmt: + return match(m, l.List, r) + case *ast.FieldList: + return match(m, l.List, r) + } + + switch r := r.(type) { + case *ast.ParenExpr: + return match(m, l, r.X) + case *ast.ExprStmt: + return match(m, l, r.X) + case *ast.DeclStmt: + return match(m, l, r.Decl) + case *ast.LabeledStmt: + return match(m, l, r.Stmt) + case *ast.BlockStmt: + if r == nil { + return match(m, l, nil) + } + return match(m, l, r.List) + case *ast.FieldList: + if r == nil { + return match(m, l, nil) + } + return match(m, l, r.List) + case *ast.BasicLit: + if r == nil { + return match(m, l, nil) + } + } + + if l, ok := l.(matcher); ok { + return l.Match(m, r) + } + + if l, ok := l.(Node); ok { + // Matching of pattern with concrete value + return matchNodeAST(m, l, r) + } + + if l == nil || r == nil { + return nil, l == r + } + + { + ln, ok1 := l.(ast.Node) + rn, ok2 := r.(ast.Node) + if ok1 && ok2 { + return matchAST(m, ln, rn) + } + } + + { + obj, ok := l.(types.Object) + if ok { + switch r := r.(type) { + case *ast.Ident: + return obj, obj == m.TypesInfo.ObjectOf(r) + case *ast.SelectorExpr: + return obj, obj == m.TypesInfo.ObjectOf(r.Sel) + default: + return obj, false + } + } + } + + { + ln, ok1 := l.([]ast.Expr) + rn, ok2 := r.([]ast.Expr) + if ok1 || ok2 { + if ok1 && !ok2 { + rn = []ast.Expr{r.(ast.Expr)} + } else if !ok1 && ok2 { + ln = []ast.Expr{l.(ast.Expr)} + } + + if len(ln) != len(rn) { + return nil, false + } + for i, ll := range ln { + if _, ok := match(m, ll, rn[i]); !ok { + return nil, false + } + } + return r, true + } + } + + { + ln, ok1 := l.([]ast.Stmt) + rn, ok2 := r.([]ast.Stmt) + if ok1 || ok2 { + if ok1 && !ok2 { + rn = []ast.Stmt{r.(ast.Stmt)} + } else if !ok1 && ok2 { + ln = []ast.Stmt{l.(ast.Stmt)} + } + + if len(ln) != len(rn) { + return nil, false + } + for i, ll := range ln { + if _, ok := match(m, ll, rn[i]); !ok { + return nil, false + } + } + return r, true + } + } + + panic(fmt.Sprintf("unsupported comparison: %T and %T", l, r)) +} + +// Match a Node with an AST node +func matchNodeAST(m *Matcher, a Node, b interface{}) (interface{}, bool) { + switch b := b.(type) { + case []ast.Stmt: + // 'a' is not a List or we'd be using its Match + // implementation. + + if len(b) != 1 { + return nil, false + } + return match(m, a, b[0]) + case []ast.Expr: + // 'a' is not a List or we'd be using its Match + // implementation. + + if len(b) != 1 { + return nil, false + } + return match(m, a, b[0]) + case ast.Node: + ra := reflect.ValueOf(a) + rb := reflect.ValueOf(b).Elem() + + if ra.Type().Name() != rb.Type().Name() { + return nil, false + } + + for i := 0; i < ra.NumField(); i++ { + af := ra.Field(i) + fieldName := ra.Type().Field(i).Name + bf := rb.FieldByName(fieldName) + if (bf == reflect.Value{}) { + panic(fmt.Sprintf("internal error: could not find field %s in type %t when comparing with %T", fieldName, b, a)) + } + ai := af.Interface() + bi := bf.Interface() + if ai == nil { + return b, bi == nil + } + if _, ok := match(m, ai.(Node), bi); !ok { + return b, false + } + } + return b, true + case nil: + return nil, a == Nil{} + default: + panic(fmt.Sprintf("unhandled type %T", b)) + } +} + +// Match two AST nodes +func matchAST(m *Matcher, a, b ast.Node) (interface{}, bool) { + ra := reflect.ValueOf(a) + rb := reflect.ValueOf(b) + + if ra.Type() != rb.Type() { + return nil, false + } + if ra.IsNil() || rb.IsNil() { + return rb, ra.IsNil() == rb.IsNil() + } + + ra = ra.Elem() + rb = rb.Elem() + for i := 0; i < ra.NumField(); i++ { + af := ra.Field(i) + bf := rb.Field(i) + if af.Type() == rtTokPos || af.Type() == rtObject || af.Type() == rtCommentGroup { + continue + } + + switch af.Kind() { + case reflect.Slice: + if af.Len() != bf.Len() { + return nil, false + } + for j := 0; j < af.Len(); j++ { + if _, ok := match(m, af.Index(j).Interface().(ast.Node), bf.Index(j).Interface().(ast.Node)); !ok { + return nil, false + } + } + case reflect.String: + if af.String() != bf.String() { + return nil, false + } + case reflect.Int: + if af.Int() != bf.Int() { + return nil, false + } + case reflect.Bool: + if af.Bool() != bf.Bool() { + return nil, false + } + case reflect.Ptr, reflect.Interface: + if _, ok := match(m, af.Interface(), bf.Interface()); !ok { + return nil, false + } + default: + panic(fmt.Sprintf("internal error: unhandled kind %s (%T)", af.Kind(), af.Interface())) + } + } + return b, true +} + +func (b Binding) Match(m *Matcher, node interface{}) (interface{}, bool) { + if isNil(b.Node) { + v, ok := m.State[b.Name] + if ok { + // Recall value + return match(m, v, node) + } + // Matching anything + b.Node = Any{} + } + + // Store value + if _, ok := m.State[b.Name]; ok { + panic(fmt.Sprintf("binding already created: %s", b.Name)) + } + new, ret := match(m, b.Node, node) + if ret { + m.State[b.Name] = new + } + return new, ret +} + +func (Any) Match(m *Matcher, node interface{}) (interface{}, bool) { + return node, true +} + +func (l List) Match(m *Matcher, node interface{}) (interface{}, bool) { + v := reflect.ValueOf(node) + if v.Kind() == reflect.Slice { + if isNil(l.Head) { + return node, v.Len() == 0 + } + if v.Len() == 0 { + return nil, false + } + // OPT(dh): don't check the entire tail if head didn't match + _, ok1 := match(m, l.Head, v.Index(0).Interface()) + _, ok2 := match(m, l.Tail, v.Slice(1, v.Len()).Interface()) + return node, ok1 && ok2 + } + // Our empty list does not equal an untyped Go nil. This way, we can + // tell apart an if with no else and an if with an empty else. + return nil, false +} + +func (s String) Match(m *Matcher, node interface{}) (interface{}, bool) { + switch o := node.(type) { + case token.Token: + if tok, ok := maybeToken(s); ok { + return match(m, tok, node) + } + return nil, false + case string: + return o, string(s) == o + default: + return nil, false + } +} + +func (tok Token) Match(m *Matcher, node interface{}) (interface{}, bool) { + o, ok := node.(token.Token) + if !ok { + return nil, false + } + return o, token.Token(tok) == o +} + +func (Nil) Match(m *Matcher, node interface{}) (interface{}, bool) { + return nil, isNil(node) +} + +func (builtin Builtin) Match(m *Matcher, node interface{}) (interface{}, bool) { + ident, ok := node.(*ast.Ident) + if !ok { + return nil, false + } + obj := m.TypesInfo.ObjectOf(ident) + if obj != types.Universe.Lookup(ident.Name) { + return nil, false + } + return match(m, builtin.Name, ident.Name) +} + +func (obj Object) Match(m *Matcher, node interface{}) (interface{}, bool) { + ident, ok := node.(*ast.Ident) + if !ok { + return nil, false + } + + id := m.TypesInfo.ObjectOf(ident) + _, ok = match(m, obj.Name, ident.Name) + return id, ok +} + +func (fn Function) Match(m *Matcher, node interface{}) (interface{}, bool) { + var name string + var obj types.Object + switch node := node.(type) { + case *ast.Ident: + obj = m.TypesInfo.ObjectOf(node) + switch obj := obj.(type) { + case *types.Func: + name = lint.FuncName(obj) + case *types.Builtin: + name = obj.Name() + default: + return nil, false + } + case *ast.SelectorExpr: + var ok bool + obj, ok = m.TypesInfo.ObjectOf(node.Sel).(*types.Func) + if !ok { + return nil, false + } + name = lint.FuncName(obj.(*types.Func)) + default: + return nil, false + } + _, ok := match(m, fn.Name, name) + return obj, ok +} + +func (or Or) Match(m *Matcher, node interface{}) (interface{}, bool) { + for _, opt := range or.Nodes { + mc := m.fork() + if ret, ok := match(mc, opt, node); ok { + m.merge(mc) + return ret, true + } + } + return nil, false +} + +func (not Not) Match(m *Matcher, node interface{}) (interface{}, bool) { + _, ok := match(m, not.Node, node) + if ok { + return nil, false + } + return node, true +} + +var ( + // Types of fields in go/ast structs that we want to skip + rtTokPos = reflect.TypeOf(token.Pos(0)) + rtObject = reflect.TypeOf((*ast.Object)(nil)) + rtCommentGroup = reflect.TypeOf((*ast.CommentGroup)(nil)) +) + +var ( + _ matcher = Binding{} + _ matcher = Any{} + _ matcher = List{} + _ matcher = String("") + _ matcher = Token(0) + _ matcher = Nil{} + _ matcher = Builtin{} + _ matcher = Object{} + _ matcher = Function{} + _ matcher = Or{} + _ matcher = Not{} +) diff --git a/vendor/honnef.co/go/tools/pattern/parser.go b/vendor/honnef.co/go/tools/pattern/parser.go new file mode 100644 index 0000000000..009238b860 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/parser.go @@ -0,0 +1,455 @@ +package pattern + +import ( + "fmt" + "go/ast" + "go/token" + "reflect" +) + +type Pattern struct { + Root Node + // Relevant contains instances of ast.Node that could potentially + // initiate a successful match of the pattern. + Relevant []reflect.Type +} + +func MustParse(s string) Pattern { + p := &Parser{AllowTypeInfo: true} + pat, err := p.Parse(s) + if err != nil { + panic(err) + } + return pat +} + +func roots(node Node) []reflect.Type { + switch node := node.(type) { + case Or: + var out []reflect.Type + for _, el := range node.Nodes { + out = append(out, roots(el)...) + } + return out + case Not: + return roots(node.Node) + case Binding: + return roots(node.Node) + case Nil, nil: + // this branch is reached via bindings + return allTypes + default: + Ts, ok := nodeToASTTypes[reflect.TypeOf(node)] + if !ok { + panic(fmt.Sprintf("internal error: unhandled type %T", node)) + } + return Ts + } +} + +var allTypes = []reflect.Type{ + reflect.TypeOf((*ast.RangeStmt)(nil)), + reflect.TypeOf((*ast.AssignStmt)(nil)), + reflect.TypeOf((*ast.IndexExpr)(nil)), + reflect.TypeOf((*ast.Ident)(nil)), + reflect.TypeOf((*ast.ValueSpec)(nil)), + reflect.TypeOf((*ast.GenDecl)(nil)), + reflect.TypeOf((*ast.BinaryExpr)(nil)), + reflect.TypeOf((*ast.ForStmt)(nil)), + reflect.TypeOf((*ast.ArrayType)(nil)), + reflect.TypeOf((*ast.DeferStmt)(nil)), + reflect.TypeOf((*ast.MapType)(nil)), + reflect.TypeOf((*ast.ReturnStmt)(nil)), + reflect.TypeOf((*ast.SliceExpr)(nil)), + reflect.TypeOf((*ast.StarExpr)(nil)), + reflect.TypeOf((*ast.UnaryExpr)(nil)), + reflect.TypeOf((*ast.SendStmt)(nil)), + reflect.TypeOf((*ast.SelectStmt)(nil)), + reflect.TypeOf((*ast.ImportSpec)(nil)), + reflect.TypeOf((*ast.IfStmt)(nil)), + reflect.TypeOf((*ast.GoStmt)(nil)), + reflect.TypeOf((*ast.Field)(nil)), + reflect.TypeOf((*ast.SelectorExpr)(nil)), + reflect.TypeOf((*ast.StructType)(nil)), + reflect.TypeOf((*ast.KeyValueExpr)(nil)), + reflect.TypeOf((*ast.FuncType)(nil)), + reflect.TypeOf((*ast.FuncLit)(nil)), + reflect.TypeOf((*ast.FuncDecl)(nil)), + reflect.TypeOf((*ast.ChanType)(nil)), + reflect.TypeOf((*ast.CallExpr)(nil)), + reflect.TypeOf((*ast.CaseClause)(nil)), + reflect.TypeOf((*ast.CommClause)(nil)), + reflect.TypeOf((*ast.CompositeLit)(nil)), + reflect.TypeOf((*ast.EmptyStmt)(nil)), + reflect.TypeOf((*ast.SwitchStmt)(nil)), + reflect.TypeOf((*ast.TypeSwitchStmt)(nil)), + reflect.TypeOf((*ast.TypeAssertExpr)(nil)), + reflect.TypeOf((*ast.TypeSpec)(nil)), + reflect.TypeOf((*ast.InterfaceType)(nil)), + reflect.TypeOf((*ast.BranchStmt)(nil)), + reflect.TypeOf((*ast.IncDecStmt)(nil)), + reflect.TypeOf((*ast.BasicLit)(nil)), +} + +var nodeToASTTypes = map[reflect.Type][]reflect.Type{ + reflect.TypeOf(String("")): nil, + reflect.TypeOf(Token(0)): nil, + reflect.TypeOf(List{}): {reflect.TypeOf((*ast.BlockStmt)(nil)), reflect.TypeOf((*ast.FieldList)(nil))}, + reflect.TypeOf(Builtin{}): {reflect.TypeOf((*ast.Ident)(nil))}, + reflect.TypeOf(Object{}): {reflect.TypeOf((*ast.Ident)(nil))}, + reflect.TypeOf(Function{}): {reflect.TypeOf((*ast.Ident)(nil)), reflect.TypeOf((*ast.SelectorExpr)(nil))}, + reflect.TypeOf(Any{}): allTypes, + reflect.TypeOf(RangeStmt{}): {reflect.TypeOf((*ast.RangeStmt)(nil))}, + reflect.TypeOf(AssignStmt{}): {reflect.TypeOf((*ast.AssignStmt)(nil))}, + reflect.TypeOf(IndexExpr{}): {reflect.TypeOf((*ast.IndexExpr)(nil))}, + reflect.TypeOf(Ident{}): {reflect.TypeOf((*ast.Ident)(nil))}, + reflect.TypeOf(ValueSpec{}): {reflect.TypeOf((*ast.ValueSpec)(nil))}, + reflect.TypeOf(GenDecl{}): {reflect.TypeOf((*ast.GenDecl)(nil))}, + reflect.TypeOf(BinaryExpr{}): {reflect.TypeOf((*ast.BinaryExpr)(nil))}, + reflect.TypeOf(ForStmt{}): {reflect.TypeOf((*ast.ForStmt)(nil))}, + reflect.TypeOf(ArrayType{}): {reflect.TypeOf((*ast.ArrayType)(nil))}, + reflect.TypeOf(DeferStmt{}): {reflect.TypeOf((*ast.DeferStmt)(nil))}, + reflect.TypeOf(MapType{}): {reflect.TypeOf((*ast.MapType)(nil))}, + reflect.TypeOf(ReturnStmt{}): {reflect.TypeOf((*ast.ReturnStmt)(nil))}, + reflect.TypeOf(SliceExpr{}): {reflect.TypeOf((*ast.SliceExpr)(nil))}, + reflect.TypeOf(StarExpr{}): {reflect.TypeOf((*ast.StarExpr)(nil))}, + reflect.TypeOf(UnaryExpr{}): {reflect.TypeOf((*ast.UnaryExpr)(nil))}, + reflect.TypeOf(SendStmt{}): {reflect.TypeOf((*ast.SendStmt)(nil))}, + reflect.TypeOf(SelectStmt{}): {reflect.TypeOf((*ast.SelectStmt)(nil))}, + reflect.TypeOf(ImportSpec{}): {reflect.TypeOf((*ast.ImportSpec)(nil))}, + reflect.TypeOf(IfStmt{}): {reflect.TypeOf((*ast.IfStmt)(nil))}, + reflect.TypeOf(GoStmt{}): {reflect.TypeOf((*ast.GoStmt)(nil))}, + reflect.TypeOf(Field{}): {reflect.TypeOf((*ast.Field)(nil))}, + reflect.TypeOf(SelectorExpr{}): {reflect.TypeOf((*ast.SelectorExpr)(nil))}, + reflect.TypeOf(StructType{}): {reflect.TypeOf((*ast.StructType)(nil))}, + reflect.TypeOf(KeyValueExpr{}): {reflect.TypeOf((*ast.KeyValueExpr)(nil))}, + reflect.TypeOf(FuncType{}): {reflect.TypeOf((*ast.FuncType)(nil))}, + reflect.TypeOf(FuncLit{}): {reflect.TypeOf((*ast.FuncLit)(nil))}, + reflect.TypeOf(FuncDecl{}): {reflect.TypeOf((*ast.FuncDecl)(nil))}, + reflect.TypeOf(ChanType{}): {reflect.TypeOf((*ast.ChanType)(nil))}, + reflect.TypeOf(CallExpr{}): {reflect.TypeOf((*ast.CallExpr)(nil))}, + reflect.TypeOf(CaseClause{}): {reflect.TypeOf((*ast.CaseClause)(nil))}, + reflect.TypeOf(CommClause{}): {reflect.TypeOf((*ast.CommClause)(nil))}, + reflect.TypeOf(CompositeLit{}): {reflect.TypeOf((*ast.CompositeLit)(nil))}, + reflect.TypeOf(EmptyStmt{}): {reflect.TypeOf((*ast.EmptyStmt)(nil))}, + reflect.TypeOf(SwitchStmt{}): {reflect.TypeOf((*ast.SwitchStmt)(nil))}, + reflect.TypeOf(TypeSwitchStmt{}): {reflect.TypeOf((*ast.TypeSwitchStmt)(nil))}, + reflect.TypeOf(TypeAssertExpr{}): {reflect.TypeOf((*ast.TypeAssertExpr)(nil))}, + reflect.TypeOf(TypeSpec{}): {reflect.TypeOf((*ast.TypeSpec)(nil))}, + reflect.TypeOf(InterfaceType{}): {reflect.TypeOf((*ast.InterfaceType)(nil))}, + reflect.TypeOf(BranchStmt{}): {reflect.TypeOf((*ast.BranchStmt)(nil))}, + reflect.TypeOf(IncDecStmt{}): {reflect.TypeOf((*ast.IncDecStmt)(nil))}, + reflect.TypeOf(BasicLit{}): {reflect.TypeOf((*ast.BasicLit)(nil))}, +} + +var requiresTypeInfo = map[string]bool{ + "Function": true, + "Builtin": true, + "Object": true, +} + +type Parser struct { + // Allow nodes that rely on type information + AllowTypeInfo bool + + lex *lexer + cur item + last *item + items chan item +} + +func (p *Parser) Parse(s string) (Pattern, error) { + p.cur = item{} + p.last = nil + p.items = nil + + fset := token.NewFileSet() + p.lex = &lexer{ + f: fset.AddFile("