diff --git a/common/utils/string_unsafe.go b/common/utils/string_unsafe.go new file mode 100644 index 00000000..7733df39 --- /dev/null +++ b/common/utils/string_unsafe.go @@ -0,0 +1,49 @@ +package utils + +import "unsafe" + +// sliceHeader is equivalent to reflect.SliceHeader, but represents the pointer +// to the underlying array as unsafe.Pointer rather than uintptr, allowing +// sliceHeaders to be directly converted to slice objects. +type sliceHeader struct { + Data unsafe.Pointer + Len int + Cap int +} + +// slice returns a slice whose underlying array starts at ptr an which length +// and capacity are len. +func slice[T any](ptr *T, length int) []T { + var s []T + hdr := (*sliceHeader)(unsafe.Pointer(&s)) + hdr.Data = unsafe.Pointer(ptr) + hdr.Len = length + hdr.Cap = length + return s +} + +// stringHeader is equivalent to reflect.StringHeader, but represents the +// pointer to the underlying array as unsafe.Pointer rather than uintptr, +// allowing StringHeaders to be directly converted to strings. +type stringHeader struct { + Data unsafe.Pointer + Len int +} + +// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the +// same memory backing s instead of making a heap-allocated copy. This is only +// valid if the returned slice is never mutated. +func ImmutableBytesFromString(s string) []byte { + shdr := (*stringHeader)(unsafe.Pointer(&s)) + return slice((*byte)(shdr.Data), shdr.Len) +} + +// StringFromImmutableBytes is equivalent to string(bs), except that it uses +// the same memory backing bs instead of making a heap-allocated copy. This is +// only valid if bs is never mutated after StringFromImmutableBytes returns. +func StringFromImmutableBytes(bs []byte) string { + // This is cheaper than messing with StringHeader and SliceHeader, which as + // of this writing produces many dead stores of zeroes. Compare + // strings.Builder.String(). + return *(*string)(unsafe.Pointer(&bs)) +} diff --git a/component/iface/iface.go b/component/iface/iface.go index c32b65ab..dca6cca1 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -24,6 +24,8 @@ var ( var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20) +const FlagRunning = 32 // interface is in running state, compatibility with golang<1.20 + func ResolveInterface(name string) (*Interface, error) { value, err, _ := interfaces.Do(func() (map[string]*Interface, error) { ifaces, err := net.Interfaces() @@ -39,7 +41,7 @@ func ResolveInterface(name string) (*Interface, error) { continue } // if not available device like Meta, dummy0, docker0, etc. - if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&net.FlagRunning == 0) { + if (iface.Flags&net.FlagMulticast == 0) || (iface.Flags&net.FlagPointToPoint != 0) || (iface.Flags&FlagRunning == 0) { continue } diff --git a/component/trie/domain_set.go b/component/trie/domain_set.go index 41ca2161..e1ad6559 100644 --- a/component/trie/domain_set.go +++ b/component/trie/domain_set.go @@ -23,6 +23,8 @@ type DomainSet struct { ranks, selects []int32 } +type qElt struct{ s, e, col int } + // NewDomainSet creates a new *DomainSet struct, from a DomainTrie. func (t *DomainTrie[T]) NewDomainSet() *DomainSet { reserveDomains := make([]string, 0) @@ -39,7 +41,6 @@ func (t *DomainTrie[T]) NewDomainSet() *DomainSet { ss := &DomainSet{} lIdx := 0 - type qElt struct{ s, e, col int } queue := []qElt{{0, len(keys), 0}} for i := 0; i < len(queue); i++ { elt := queue[i] diff --git a/go.mod b/go.mod index 29dd4b86..89071513 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Dreamacro/clash -go 1.20 +go 1.19 require ( github.com/3andne/restls-client-go v0.1.4 diff --git a/hub/route/server.go b/hub/route/server.go index 8ccb79f5..d2fecd05 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -9,13 +9,14 @@ import ( "runtime/debug" "strings" "time" - "unsafe" "github.com/Dreamacro/clash/adapter/inbound" CN "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/common/utils" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel/statistic" + "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" @@ -152,8 +153,8 @@ func Start(addr string, tlsAddr string, secret string, } func safeEuqal(a, b string) bool { - aBuf := unsafe.Slice(unsafe.StringData(a), len(a)) - bBuf := unsafe.Slice(unsafe.StringData(b), len(b)) + aBuf := utils.ImmutableBytesFromString(a) + bBuf := utils.ImmutableBytesFromString(b) return subtle.ConstantTimeCompare(aBuf, bBuf) == 1 }