2
0
mirror of synced 2025-02-24 23:38:22 +00:00
Elias Naur 16fd47fa04 bind: don't inherit java.lang.Object methods to Java interfaces
Before, the Java generator let Java interfaces inherit java.lang.Object
methods. However, interfaces strictly doesn't inherit Object and since
the JNI GetMethodID returns NULL for Object methods on interface classes,
stop making Object a super class to interfaces.

Change-Id: I3757c1ed02c07ccffab74a30132d5197742c6513
Reviewed-on: https://go-review.googlesource.com/30096
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
2016-10-01 18:55:04 +00:00

876 lines
19 KiB
Go

// 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.
// The java package takes the result of an AST traversal by the
// importers package and queries the java command for the type
// information for the referenced Java classes and interfaces.
//
// It is the of go/types for Java types and is used by the bind
// package to generate Go wrappers for Java API on Android.
package java
import (
"bufio"
"bytes"
"errors"
"fmt"
"os/exec"
"strings"
"unicode"
"unicode/utf8"
"golang.org/x/mobile/internal/importers"
)
// Class is the bind representation of a Java class or
// interface.
// Use Import to convert class references to Class.
type Class struct {
// "java.pkg.Class.Inner"
Name string
// "java.pkg.Class$Inner"
FindName string
// JNI mangled name
JNIName string
// "Inner"
PkgName string
Funcs []*Func
Methods []*Func
// All methods, including methods from
// supers.
AllMethods []*Func
Vars []*Var
Supers []string
Final bool
Abstract bool
Interface bool
Throwable bool
// Whether the class has a no-arg constructor
HasNoArgCon bool
}
// Func is a Java static function or method or constructor.
type Func struct {
FuncSig
ArgDesc string
GoName string
// Mangled JNI name
JNIName string
Static bool
Abstract bool
Final bool
Public bool
Constructor bool
Params []*Type
Ret *Type
Decl string
Throws string
}
// FuncSig uniquely identifies a Java Func.
type FuncSig struct {
Name string
// The method descriptor, in JNI format.
Desc string
}
// Var is a Java member variable.
type Var struct {
Name string
Static bool
Final bool
Val string
Type *Type
}
// Type is a Java type.
type Type struct {
Kind TypeKind
Class string
Elem *Type
}
type TypeKind int
type importer struct {
bootclspath string
clspath string
clsMap map[string]*Class
}
const (
Int TypeKind = iota
Boolean
Short
Char
Byte
Long
Float
Double
String
Array
Object
)
var (
errClsNotFound = errors.New("class not found")
)
// IsAvailable returns whether the required tools are available for
// Import to work. In particular, IsAvailable checks the existence
// of the javap binary.
func IsAvailable() bool {
_, err := javapPath()
return err == nil
}
func javapPath() (string, error) {
return exec.LookPath("javap")
}
// Import returns Java Class descriptors for a list of references.
//
// The javap command from the Java SDK is used to dump
// class information. Its output looks like this:
//
// Compiled from "System.java"
// public final class java.lang.System {
// public static final java.io.InputStream in;
// descriptor: Ljava/io/InputStream;
// public static final java.io.PrintStream out;
// descriptor: Ljava/io/PrintStream;
// public static final java.io.PrintStream err;
// descriptor: Ljava/io/PrintStream;
// public static void setIn(java.io.InputStream);
// descriptor: (Ljava/io/InputStream;)V
//
// ...
//
// }
func Import(bootclasspath, classpath string, refs *importers.References) ([]*Class, error) {
imp := &importer{
bootclspath: bootclasspath,
clspath: classpath,
clsMap: make(map[string]*Class),
}
clsSet := make(map[string]struct{})
var names []string
for _, ref := range refs.Refs {
// The reference could be to some/pkg.Class or some/pkg/Class.Identifier. Include both.
pkg := strings.Replace(ref.Pkg, "/", ".", -1)
for _, cls := range []string{pkg, pkg + "." + ref.Name} {
if _, exists := clsSet[cls]; !exists {
clsSet[cls] = struct{}{}
names = append(names, cls)
}
}
}
classes, err := imp.importClasses(names, true)
if err != nil {
return nil, err
}
for _, cls := range classes {
imp.fillAllMethods(cls)
}
imp.fillThrowables()
for _, cls := range classes {
imp.mangleOverloads(cls.AllMethods)
imp.mangleOverloads(cls.Funcs)
}
imp.filterReferences(classes, refs)
return classes, nil
}
func (v *Var) Constant() bool {
return v.Static && v.Final && v.Val != ""
}
// Mangle a name according to
// http://docs.oracle.com/javase/6/docs/technotes/guides/jni/spec/design.html#wp16696
//
// TODO: Support unicode characters
func JNIMangle(s string) string {
var m []byte
for i := 0; i < len(s); i++ {
switch c := s[i]; c {
case '.', '/':
m = append(m, '_')
case '$':
m = append(m, "_00024"...)
case '_':
m = append(m, "_1"...)
case ';':
m = append(m, "_2"...)
case '[':
m = append(m, "_3"...)
default:
m = append(m, c)
}
}
return string(m)
}
func (c *Class) HasSuper() bool {
return !c.Final && !c.Interface
}
func (t *Type) Type() string {
switch t.Kind {
case Int:
return "int"
case Boolean:
return "boolean"
case Short:
return "short"
case Char:
return "char"
case Byte:
return "byte"
case Long:
return "long"
case Float:
return "float"
case Double:
return "double"
case String:
return "String"
case Array:
return t.Elem.Type() + "[]"
case Object:
return t.Class
default:
panic("invalid kind")
}
}
func (t *Type) JNIType() string {
switch t.Kind {
case Int:
return "jint"
case Boolean:
return "jboolean"
case Short:
return "jshort"
case Char:
return "jchar"
case Byte:
return "jbyte"
case Long:
return "jlong"
case Float:
return "jfloat"
case Double:
return "jdouble"
case String:
return "jstring"
case Array:
return "jarray"
case Object:
return "jobject"
default:
panic("invalid kind")
}
}
func (t *Type) CType() string {
switch t.Kind {
case Int, Boolean, Short, Char, Byte, Long, Float, Double:
return t.JNIType()
case String:
return "nstring"
case Array:
if t.Elem.Kind != Byte {
panic("unsupported array type")
}
return "nbyteslice"
case Object:
return "jint"
default:
panic("invalid kind")
}
}
func (t *Type) JNICallType() string {
switch t.Kind {
case Int:
return "Int"
case Boolean:
return "Boolean"
case Short:
return "Short"
case Char:
return "Char"
case Byte:
return "Byte"
case Long:
return "Long"
case Float:
return "Float"
case Double:
return "Double"
case String, Object, Array:
return "Object"
default:
panic("invalid kind")
}
}
func (j *importer) filterReferences(classes []*Class, refs *importers.References) {
refFuncs := make(map[[2]string]struct{})
for _, ref := range refs.Refs {
pkgName := strings.Replace(ref.Pkg, "/", ".", -1)
cls := j.clsMap[pkgName]
if cls == nil {
continue
}
refFuncs[[...]string{pkgName, ref.Name}] = struct{}{}
}
for _, cls := range classes {
var filtered []*Func
for _, f := range cls.Funcs {
if _, exists := refFuncs[[...]string{cls.Name, f.GoName}]; exists {
filtered = append(filtered, f)
}
}
cls.Funcs = filtered
filtered = nil
for _, m := range cls.Methods {
if _, exists := refs.Names[m.GoName]; exists {
filtered = append(filtered, m)
}
}
cls.Methods = filtered
filtered = nil
for _, m := range cls.AllMethods {
if _, exists := refs.Names[m.GoName]; exists {
filtered = append(filtered, m)
}
}
cls.AllMethods = filtered
}
}
func (j *importer) importClasses(names []string, allowMissingClasses bool) ([]*Class, error) {
if len(names) == 0 {
return nil, nil
}
args := []string{"-s", "-protected", "-constants"}
if j.clspath != "" {
args = append(args, "-classpath", j.clspath)
}
if j.bootclspath != "" {
args = append(args, "-bootclasspath", j.bootclspath)
}
args = append(args, names...)
javapPath, err := javapPath()
if err != nil {
return nil, err
}
javap := exec.Command(javapPath, args...)
out, err := javap.CombinedOutput()
if err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return nil, fmt.Errorf("javap failed: %v: %s", err)
}
// Not every name is a Java class so an exit error from javap is not
// fatal.
}
s := bufio.NewScanner(bytes.NewBuffer(out))
var classes []*Class
for _, name := range names {
cls, err := j.scanClass(s, name)
if err != nil {
if allowMissingClasses && err == errClsNotFound {
continue
}
return nil, err
}
classes = append(classes, cls)
j.clsMap[name] = cls
}
// Include the methods from classes extended or implemented
unkCls := classes
for {
var unknown []string
for _, cls := range unkCls {
unknown = j.unknownSuperClasses(cls, unknown)
}
if len(unknown) == 0 {
break
}
newCls, err := j.importClasses(unknown, false)
if err != nil {
return nil, err
}
for _, cls := range newCls {
j.clsMap[cls.Name] = cls
}
unkCls = newCls
}
return classes, nil
}
func (j *importer) unknownSuperClasses(cls *Class, unk []string) []string {
loop:
for _, n := range cls.Supers {
if s, exists := j.clsMap[n]; exists {
unk = j.unknownSuperClasses(s, unk)
} else {
for _, u := range unk {
if u == n {
continue loop
}
}
unk = append(unk, n)
}
}
return unk
}
func (j *importer) scanClass(s *bufio.Scanner, name string) (*Class, error) {
if !s.Scan() {
return nil, fmt.Errorf("%s: missing javap header", name)
}
head := s.Text()
if errPref := "Error: "; strings.HasPrefix(head, errPref) {
msg := head[len(errPref):]
if strings.HasPrefix(msg, "class not found: "+name) {
return nil, errClsNotFound
}
return nil, errors.New(msg)
}
if !strings.HasPrefix(head, "Compiled from ") {
return nil, fmt.Errorf("%s: unexpected header: %s", name, head)
}
if !s.Scan() {
return nil, fmt.Errorf("%s: missing javap class declaration", name)
}
clsDecl := s.Text()
cls, err := j.scanClassDecl(name, clsDecl)
if err != nil {
return nil, err
}
if len(cls.Supers) == 0 {
if name == "java.lang.Object" {
cls.HasNoArgCon = true
} else if !cls.Interface {
cls.Supers = append(cls.Supers, "java.lang.Object")
}
}
cls.JNIName = JNIMangle(cls.Name)
clsElems := strings.Split(cls.Name, ".")
cls.PkgName = clsElems[len(clsElems)-1]
var funcs []*Func
for s.Scan() {
decl := strings.TrimSpace(s.Text())
if decl == "}" {
break
} else if decl == "" {
continue
}
if !s.Scan() {
return nil, fmt.Errorf("%s: missing descriptor for member %q", name, decl)
}
desc := strings.TrimSpace(s.Text())
desc = strings.TrimPrefix(desc, "descriptor: ")
var static, final, abstract, public bool
// Trim modifiders from the declaration.
loop:
for {
idx := strings.Index(decl, " ")
if idx == -1 {
break
}
keyword := decl[:idx]
switch keyword {
case "public":
public = true
case "protected", "native":
// ignore
case "static":
static = true
case "final":
final = true
case "abstract":
abstract = true
default:
// Hopefully we reached the declaration now.
break loop
}
decl = decl[idx+1:]
}
// Trim ending ;
decl = decl[:len(decl)-1]
if idx := strings.Index(decl, "("); idx != -1 {
f, err := j.scanMethod(decl, desc, idx)
if err != nil {
return nil, fmt.Errorf("%s: %v", name, err)
}
if f != nil {
f.Static = static
f.Abstract = abstract
f.Public = public || cls.Interface
f.Final = final
f.Constructor = f.Name == cls.FindName
if f.Constructor {
cls.HasNoArgCon = cls.HasNoArgCon || len(f.Params) == 0
f.Public = f.Public && !cls.Abstract
f.Name = "new"
f.Ret = &Type{Class: name, Kind: Object}
}
funcs = append(funcs, f)
}
} else {
// Member is a variable
v, err := j.scanVar(decl, desc)
if err != nil {
return nil, fmt.Errorf("%s: %v", name, err)
}
if v != nil && public {
v.Static = static
v.Final = final
cls.Vars = append(cls.Vars, v)
}
}
}
for _, f := range funcs {
if f.Static || f.Constructor {
cls.Funcs = append(cls.Funcs, f)
} else {
cls.Methods = append(cls.Methods, f)
}
}
return cls, nil
}
func (j *importer) scanClassDecl(name string, decl string) (*Class, error) {
cls := &Class{
Name: name,
}
const (
stMod = iota
stName
stExt
stImpl
)
st := stMod
var w []byte
// if > 0, we're inside a generics declaration
gennest := 0
for i := 0; i < len(decl); i++ {
c := decl[i]
switch c {
default:
if gennest == 0 {
w = append(w, c)
}
case '>':
gennest--
case '<':
gennest++
case '{':
return cls, nil
case ' ', ',':
if gennest > 0 {
break
}
switch w := string(w); w {
default:
switch st {
case stName:
if strings.Replace(w, "$", ".", -1) != strings.Replace(name, "$", ".", -1) {
return nil, fmt.Errorf("unexpected name %q in class declaration: %q", w, decl)
}
cls.FindName = w
case stExt:
cls.Supers = append(cls.Supers, w)
case stImpl:
if !cls.Interface {
cls.Supers = append(cls.Supers, w)
}
default:
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
case "":
// skip
case "public":
if st != stMod {
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
case "abstract":
if st != stMod {
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
cls.Abstract = true
case "final":
if st != stMod {
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
cls.Final = true
case "interface":
cls.Interface = true
fallthrough
case "class":
if st != stMod {
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
st = stName
case "extends":
if st != stName {
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
st = stExt
case "implements":
if st != stName && st != stExt {
return nil, fmt.Errorf("unexpected %q in class declaration: %q", w, decl)
}
st = stImpl
}
w = w[:0]
}
}
return nil, fmt.Errorf("missing ending { in class declaration: %q", decl)
}
func (j *importer) scanVar(decl, desc string) (*Var, error) {
v := new(Var)
const eq = " = "
idx := strings.Index(decl, eq)
if idx != -1 {
val, ok := j.parseJavaValue(decl[idx+len(eq):])
if !ok {
// Skip constants that cannot be represented in Go
return nil, nil
}
v.Val = val
} else {
idx = len(decl)
}
for i := idx - 1; i >= 0; i-- {
if i == 0 || decl[i-1] == ' ' {
v.Name = decl[i:idx]
break
}
}
if v.Name == "" {
return nil, fmt.Errorf("unable to parse member name from declaration: %q", decl)
}
typ, _, err := j.parseJavaType(desc)
if err != nil {
return nil, fmt.Errorf("invalid type signature for %s: %q", v.Name, desc)
}
v.Type = typ
return v, nil
}
func (j *importer) scanMethod(decl, desc string, parenIdx int) (*Func, error) {
// Member is a method
f := new(Func)
f.Desc = desc
for i := parenIdx - 1; i >= 0; i-- {
if i == 0 || decl[i-1] == ' ' {
f.Name = decl[i:parenIdx]
break
}
}
if f.Name == "" {
return nil, fmt.Errorf("unable to parse method name from declaration: %q", decl)
}
if desc[0] != '(' {
return nil, fmt.Errorf("invalid descriptor for method %s: %q", f.Name, desc)
}
const throws = " throws "
if idx := strings.Index(decl, throws); idx != -1 {
f.Throws = decl[idx+len(throws):]
}
i := 1
for desc[i] != ')' {
typ, n, err := j.parseJavaType(desc[i:])
if err != nil {
return nil, fmt.Errorf("invalid descriptor for method %s: %v", f.Name, err)
}
i += n
f.Params = append(f.Params, typ)
}
f.ArgDesc = desc[1:i]
i++ // skip ending )
if desc[i] != 'V' {
typ, _, err := j.parseJavaType(desc[i:])
if err != nil {
return nil, fmt.Errorf("invalid descriptor for method %s: %v", f.Name, err)
}
f.Ret = typ
}
return f, nil
}
func (j *importer) fillThrowables() {
thrCls, ok := j.clsMap["java.lang.Throwable"]
if !ok {
// If Throwable isn't in the class map
// no imported class inherits from Throwable
return
}
for _, cls := range j.clsMap {
j.fillThrowableFor(cls, thrCls)
}
}
func (j *importer) fillThrowableFor(cls, thrCls *Class) {
if cls.Interface || cls.Throwable {
return
}
cls.Throwable = cls == thrCls
for _, name := range cls.Supers {
sup := j.clsMap[name]
j.fillThrowableFor(sup, thrCls)
cls.Throwable = cls.Throwable || sup.Throwable
}
}
func (j *importer) fillAllMethods(cls *Class) {
if len(cls.AllMethods) > 0 {
return
}
if len(cls.Supers) == 0 {
cls.AllMethods = cls.Methods
return
}
for _, supName := range cls.Supers {
super := j.clsMap[supName]
j.fillAllMethods(super)
}
methods := make(map[FuncSig]struct{})
for _, supName := range cls.Supers {
super := j.clsMap[supName]
for _, f := range super.AllMethods {
if _, exists := methods[f.FuncSig]; !exists {
methods[f.FuncSig] = struct{}{}
// Copy function so each class can have its own
// JNI name mangling.
cpf := *f
cls.AllMethods = append(cls.AllMethods, &cpf)
}
}
}
for _, f := range cls.Methods {
if _, exists := methods[f.FuncSig]; !exists {
cls.AllMethods = append(cls.AllMethods, f)
}
}
}
// mangleOverloads assigns unique names to overloaded Java functions by appending
// the argument count. If multiple methods have the same name and argument count,
// the method signature is appended in JNI mangled form.
func (j *importer) mangleOverloads(allFuncs []*Func) {
overloads := make(map[string][]*Func)
for _, f := range allFuncs {
overloads[f.Name] = append(overloads[f.Name], f)
}
for _, funcs := range overloads {
for _, f := range funcs {
f.GoName = initialUpper(f.Name)
f.JNIName = JNIMangle(f.Name)
}
if len(funcs) == 1 {
continue
}
lengths := make(map[int]int)
for _, f := range funcs {
f.JNIName += "__" + JNIMangle(f.ArgDesc)
lengths[len(f.Params)]++
}
for _, f := range funcs {
n := len(f.Params)
if lengths[n] > 1 {
f.GoName += "_" + JNIMangle(f.ArgDesc)
continue
}
if n > 0 {
f.GoName = fmt.Sprintf("%s%d", f.GoName, n)
}
}
}
}
func (j *importer) parseJavaValue(v string) (string, bool) {
v = strings.TrimRight(v, "df")
switch v {
case "", "NaN", "Infinity", "-Infinity":
return "", false
default:
if v[0] == '\'' {
// Skip character constants, since they can contain invalid code points
// that are unacceptable to Go.
return "", false
}
return v, true
}
}
func (j *importer) parseJavaType(desc string) (*Type, int, error) {
t := new(Type)
var n int
if desc == "" {
return t, n, errors.New("empty type signature")
}
n++
switch desc[0] {
case 'Z':
t.Kind = Boolean
case 'B':
t.Kind = Byte
case 'C':
t.Kind = Char
case 'S':
t.Kind = Short
case 'I':
t.Kind = Int
case 'J':
t.Kind = Long
case 'F':
t.Kind = Float
case 'D':
t.Kind = Double
case 'L':
var clsName string
for i := n; i < len(desc); i++ {
if desc[i] == ';' {
clsName = strings.Replace(desc[n:i], "/", ".", -1)
n += i - n + 1
break
}
}
if clsName == "" {
return t, n, errors.New("missing ; in class type signature")
}
if clsName == "java.lang.String" {
t.Kind = String
} else {
t.Kind = Object
t.Class = clsName
}
case '[':
et, n2, err := j.parseJavaType(desc[n:])
if err != nil {
return t, n, err
}
n += n2
t.Kind = Array
t.Elem = et
default:
return t, n, fmt.Errorf("invalid type signature: %s", desc)
}
return t, n, nil
}
func initialUpper(s string) string {
if s == "" {
return ""
}
r, n := utf8.DecodeRuneInString(s)
return string(unicode.ToUpper(r)) + s[n:]
}