Skip to content

Commit 2d628c0

Browse files
committed
(multi-os-engine/multi-os-engine#160) Warn if same ObjC class is preregistered with multiple Java hybrid classes
1 parent 1886b6d commit 2d628c0

File tree

3 files changed

+199
-129
lines changed

3 files changed

+199
-129
lines changed

src/main/java/org/moe/gradle/internal/AnnotationChecker.java

Lines changed: 0 additions & 124 deletions
This file was deleted.
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
Copyright (C) 2016 Migeran
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package org.moe.gradle.internal;
18+
19+
import org.gradle.api.GradleException;
20+
import org.moe.gradle.anns.NotNull;
21+
import org.moe.gradle.anns.Nullable;
22+
import org.moe.gradle.utils.Require;
23+
import org.objectweb.asm.AnnotationVisitor;
24+
import org.objectweb.asm.ClassReader;
25+
import org.objectweb.asm.ClassVisitor;
26+
import org.objectweb.asm.Opcodes;
27+
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
31+
public class RegisterOnStartupChecker {
32+
33+
private static final String ANN_REGISTER_ON_STARTUP = "Lorg/moe/natj/general/ann/RegisterOnStartup;";
34+
private static final String ANN_OBJC_CLASS_BINDING = "Lorg/moe/natj/objc/ann/ObjCClassBinding;";
35+
private static final String ANN_OBJC_CLASS_NAME = "Lorg/moe/natj/objc/ann/ObjCClassName;";
36+
37+
/**
38+
* Returns a RegisterOnStartupChecker for the specified input stream.
39+
*
40+
* @param inputStream input stream
41+
* @return RegisterOnStartupChecker
42+
*/
43+
public static RegisterOnStartupChecker getRegisterOnStartupChecker(InputStream inputStream) {
44+
RegisterOnStartupChecker checker = new RegisterOnStartupChecker(inputStream);
45+
checker.check();
46+
return checker;
47+
}
48+
49+
/**
50+
* Input stream containing class data.
51+
*/
52+
@NotNull
53+
private final InputStream inputStream;
54+
55+
/**
56+
* Name of the java class.
57+
*/
58+
@Nullable
59+
private String javaClassName;
60+
61+
/**
62+
* The corresponding ObjC class name, if this is a hybrid ObjC class.
63+
*/
64+
@Nullable
65+
private String objCClassName;
66+
67+
/**
68+
* Boolean indicating the search result.
69+
*/
70+
private boolean isRegisterOnStartup;
71+
72+
/**
73+
* Creates a new RegisterOnStartupChecker for the specified steam.
74+
*
75+
* @param inputStream input stream to check
76+
*/
77+
private RegisterOnStartupChecker(InputStream inputStream) {
78+
this.inputStream = Require.nonNull(inputStream);
79+
}
80+
81+
/**
82+
* Returns the Java class' name.
83+
*
84+
* @return class' name
85+
*/
86+
@NotNull
87+
public String getJavaClassName() {
88+
return Require.nonNull(javaClassName);
89+
}
90+
91+
/**
92+
* Returns the ObjC class' name
93+
*
94+
* @return class' name
95+
*/
96+
@Nullable
97+
public String getObjCClassName() {
98+
return objCClassName;
99+
}
100+
101+
/**
102+
* Returns the result of the search.
103+
*
104+
* @return true if the class should be registered on startup, otherwise false
105+
*/
106+
public boolean isRegisterOnStartup() {
107+
return isRegisterOnStartup;
108+
}
109+
110+
/**
111+
* Checks for the RegisterOnStartup annotation.
112+
*/
113+
private void check() {
114+
ClassReader reader;
115+
try {
116+
reader = new ClassReader(inputStream);
117+
} catch (IOException e) {
118+
throw new GradleException("Failed to create ClassReader", e);
119+
}
120+
reader.accept(new ClassVisitor(Opcodes.ASM5) {
121+
122+
private boolean isObjCBinding = false;
123+
private String objCClassName = null;
124+
125+
@Override
126+
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
127+
AnnotationVisitor av = super.visitAnnotation(desc, visible);
128+
switch (desc) {
129+
case ANN_REGISTER_ON_STARTUP:
130+
RegisterOnStartupChecker.this.isRegisterOnStartup = true;
131+
break;
132+
case ANN_OBJC_CLASS_BINDING:
133+
isObjCBinding = true;
134+
break;
135+
case ANN_OBJC_CLASS_NAME:
136+
av = new AnnotationVisitor(Opcodes.ASM5, av) {
137+
@Override
138+
public void visit(String name, Object value) {
139+
if ("value".equals(name)) {
140+
objCClassName = (String) Require.nonNull(value);
141+
}
142+
super.visit(name, value);
143+
}
144+
};
145+
break;
146+
}
147+
return av;
148+
}
149+
150+
@Override
151+
public void visit(int version, int access, String name, String signature,
152+
String superName, String[] interfaces) {
153+
RegisterOnStartupChecker.this.javaClassName = Require.nonNull(name);
154+
super.visit(version, access, name, signature, superName, interfaces);
155+
}
156+
157+
@Override
158+
public void visitEnd() {
159+
if (isRegisterOnStartup && !isObjCBinding) {
160+
if (objCClassName == null) {
161+
objCClassName = getJavaClassName().replace('/', '.');
162+
}
163+
RegisterOnStartupChecker.this.objCClassName = objCClassName;
164+
}
165+
super.visitEnd();
166+
}
167+
}, 0);
168+
}
169+
}

src/main/java/org/moe/gradle/tasks/StartupProvider.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.moe.gradle.anns.IgnoreUnused;
2727
import org.moe.gradle.anns.NotNull;
2828
import org.moe.gradle.anns.Nullable;
29-
import org.moe.gradle.internal.AnnotationChecker;
29+
import org.moe.gradle.internal.RegisterOnStartupChecker;
3030
import org.moe.gradle.utils.FileUtils;
3131
import org.moe.gradle.utils.Mode;
3232
import org.moe.gradle.utils.Require;
@@ -37,7 +37,9 @@
3737
import java.nio.file.Path;
3838
import java.nio.file.Paths;
3939
import java.util.Collection;
40+
import java.util.HashMap;
4041
import java.util.HashSet;
42+
import java.util.LinkedHashSet;
4143
import java.util.Set;
4244
import java.util.jar.JarFile;
4345

@@ -79,6 +81,7 @@ protected void run() {
7981
try {
8082
FileUtils.deleteFileOrFolder(getPreregisterFile());
8183

84+
HashMap<String, LinkedHashSet<String>> nativeClassNames = new HashMap<>();
8285
try (FileWriter log = new FileWriter(getLogFile(), true);
8386
FileWriter txt = new FileWriter(getPreregisterFile())) {
8487
getInputFiles().forEach(it -> {
@@ -95,16 +98,38 @@ protected void run() {
9598
return;
9699
}
97100

98-
AnnotationChecker checker = AnnotationChecker.getRegisterOnStartupChecker(file.getInputStream(entry));
99-
if (checker.hasAnnotation()) {
100-
log.append("Found: ").append(checker.getName()).append("\n");
101-
txt.append(checker.getName()).append("\n");
101+
RegisterOnStartupChecker checker = RegisterOnStartupChecker.getRegisterOnStartupChecker(file.getInputStream(entry));
102+
if (checker.isRegisterOnStartup()) {
103+
log.append("Found: ").append(checker.getJavaClassName()).append("\n");
104+
txt.append(checker.getJavaClassName()).append("\n");
105+
106+
if (checker.getObjCClassName() != null) {
107+
nativeClassNames
108+
.computeIfAbsent(checker.getObjCClassName(), k -> new LinkedHashSet<>())
109+
.add(checker.getJavaClassName());
110+
}
102111
}
103112
} catch (IOException e) {
104113
throw new GradleException("An IOException occurred", e);
105114
}
106115
});
107116
});
117+
118+
nativeClassNames
119+
.entrySet()
120+
.stream()
121+
.filter(entry -> entry.getValue().size() > 1)
122+
.forEach(entry -> {
123+
String warn = "ObjC class \"" + entry.getKey()
124+
+ "\" is preregistered with multiple hybrid Java classes: ["
125+
+ String.join(", ", entry.getValue()) + "], this might cause crash at runtime!";
126+
getLogger().warn(warn);
127+
try {
128+
log.append("WARN: ").append(warn);
129+
} catch (IOException e) {
130+
throw new GradleException("An IOException occurred", e);
131+
}
132+
});
108133
}
109134
} catch (IOException e) {
110135
throw new GradleException("An IOException occurred", e);

0 commit comments

Comments
 (0)