Properly handle checkcast to array type in dependency analyzer

This commit is contained in:
Alexey Andreev 2018-07-03 19:11:51 +03:00
parent 3653e39bec
commit 4e20a1de18
12 changed files with 149 additions and 21 deletions

View File

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
@ -109,7 +110,7 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements
} }
private void parseServiceFile(DependencyAgent agent, String service, InputStream input) throws IOException { private void parseServiceFile(DependencyAgent agent, String service, InputStream input) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
while (true) { while (true) {
String line = reader.readLine(); String line = reader.readLine();
if (line == null) { if (line == null) {
@ -119,12 +120,7 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements
if (line.startsWith("#") || line.isEmpty()) { if (line.startsWith("#") || line.isEmpty()) {
continue; continue;
} }
List<String> implementors = serviceMap.get(service); serviceMap.computeIfAbsent(service, k -> new ArrayList<>()).add(line);
if (implementors == null) {
implementors = new ArrayList<>();
serviceMap.put(service, implementors);
}
implementors.add(line);
allClassesNode.propagate(agent.getType(line)); allClassesNode.propagate(agent.getType(line));
} }
} }
@ -133,7 +129,7 @@ public class ServiceLoaderSupport extends AbstractDependencyListener implements
public void methodReached(final DependencyAgent agent, MethodDependency method, final CallLocation location) { public void methodReached(final DependencyAgent agent, MethodDependency method, final CallLocation location) {
MethodReference ref = method.getReference(); MethodReference ref = method.getReference();
if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) {
method.getResult().propagate(agent.getType("[java.lang.Object")); method.getResult().propagate(agent.getType("[Ljava/lang/Object;"));
allClassesNode.connect(method.getResult().getArrayItem()); allClassesNode.connect(method.getResult().getArrayItem());
method.getResult().getArrayItem().addConsumer(type -> initConstructor(agent, type.getName(), location)); method.getResult().getArrayItem().addConsumer(type -> initConstructor(agent, type.getName(), location));
} }

View File

@ -76,6 +76,9 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
case "getInterfaces": case "getInterfaces":
reachGetInterfaces(agent, method); reachGetInterfaces(agent, method);
break; break;
case "getComponentType":
reachGetComponentType(agent, method);
break;
} }
} }
@ -109,6 +112,19 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
}); });
} }
private void reachGetComponentType(DependencyAgent agent, MethodDependency method) {
method.getVariable(0).getClassValueNode().addConsumer(t -> {
if (!t.getName().startsWith("[")) {
return;
}
String typeName = t.getName().substring(1);
if (typeName.charAt(0) == 'L') {
typeName = ((ValueType.Object) ValueType.parse(typeName)).getClassName();
}
method.getResult().getClassValueNode().propagate(agent.getType(typeName));
});
}
private void generateCreateMetadata(GeneratorContext context, SourceWriter writer) throws IOException { private void generateCreateMetadata(GeneratorContext context, SourceWriter writer) throws IOException {
ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class); ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class);
for (String className : reflection.getClassesWithReflectableFields()) { for (String className : reflection.getClassesWithReflectableFields()) {

View File

@ -179,6 +179,7 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
return (platformClass.getMetadata().getFlags() & Flags.INTERFACE) != 0; return (platformClass.getMetadata().getFlags() & Flags.INTERFACE) != 0;
} }
@PluggableDependency(ClassGenerator.class)
public TClass<?> getComponentType() { public TClass<?> getComponentType() {
return getClass(Platform.getArrayItem(platformClass)); return getClass(Platform.getArrayItem(platformClass));
} }

View File

@ -42,11 +42,16 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin {
case "getLength": case "getLength":
achieveGetLength(agent, method); achieveGetLength(agent, method);
break; break;
case "newInstanceImpl": case "newInstance":
method.getResult().propagate(agent.getType("[java.lang.Object")); method.getVariable(1).getClassValueNode().addConsumer(t -> {
String arrayTypeName = t.getName().startsWith("[")
? t.getName()
: ValueType.object(t.getName()).toString();
method.getResult().propagate(agent.getType("[" + arrayTypeName));
});
break; break;
case "getImpl": case "getImpl":
achieveGet(agent, method); reachGet(agent, method);
break; break;
} }
} }
@ -121,7 +126,7 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin {
writer.outdent().append("}").softNewLine(); writer.outdent().append("}").softNewLine();
} }
private void achieveGet(final DependencyAgent agent, final MethodDependency method) { private void reachGet(DependencyAgent agent, MethodDependency method) {
method.getVariable(1).getArrayItem().connect(method.getResult()); method.getVariable(1).getArrayItem().connect(method.getResult());
method.getVariable(1).addConsumer(type -> { method.getVariable(1).addConsumer(type -> {
if (type.getName().startsWith("[")) { if (type.getName().startsWith("[")) {

View File

@ -47,6 +47,7 @@ public final class TArray extends TObject {
return array.size; return array.size;
} }
@PluggableDependency(ArrayNativeGenerator.class)
public static TObject newInstance(TClass<?> componentType, int length) throws TNegativeArraySizeException { public static TObject newInstance(TClass<?> componentType, int length) throws TNegativeArraySizeException {
if (componentType == null) { if (componentType == null) {
throw new TNullPointerException(); throw new TNullPointerException();
@ -61,7 +62,6 @@ public final class TArray extends TObject {
} }
@GeneratedBy(ArrayNativeGenerator.class) @GeneratedBy(ArrayNativeGenerator.class)
@PluggableDependency(ArrayNativeGenerator.class)
@DelegateTo("newInstanceLowLevel") @DelegateTo("newInstanceLowLevel")
private static native TObject newInstanceImpl(PlatformClass componentType, int length); private static native TObject newInstanceImpl(PlatformClass componentType, int length);

View File

@ -88,7 +88,7 @@ public class DependencyAnalyzer implements DependencyInfo {
Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>(); Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>();
Map<MethodReference, DependencyPlugin> dependencyPlugins = new HashMap<>(); Map<MethodReference, DependencyPlugin> dependencyPlugins = new HashMap<>();
private boolean completing; private boolean completing;
private Map<String, SuperClassFilter> superClassFilters = new HashMap<>(); private Map<String, DependencyTypeFilter> superClassFilters = new HashMap<>();
boolean asyncSupported; boolean asyncSupported;
public DependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, public DependencyAnalyzer(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services,
@ -591,7 +591,9 @@ public class DependencyAnalyzer implements DependencyInfo {
if (tasks.isEmpty()) { if (tasks.isEmpty()) {
break; break;
} }
tasks.remove().run(); while (!tasks.isEmpty()) {
tasks.remove().run();
}
if (++index == 100) { if (++index == 100) {
if (interruptor != null && !interruptor.shouldContinue()) { if (interruptor != null && !interruptor.shouldContinue()) {
interrupted = true; interrupted = true;
@ -729,7 +731,24 @@ public class DependencyAnalyzer implements DependencyInfo {
dependencyPlugins.put(method, dependencyPlugin); dependencyPlugins.put(method, dependencyPlugin);
} }
SuperClassFilter getSuperClassFilter(String superClass) { DependencyTypeFilter getSuperClassFilter(String superClass) {
return superClassFilters.computeIfAbsent(superClass, s -> new SuperClassFilter(classSource, s)); DependencyTypeFilter result = superClassFilters.get(superClass);
if (result == null) {
if (superClass.startsWith("[")) {
char second = superClass.charAt(1);
if (second == '[') {
result = new SuperArrayFilter(this, getSuperClassFilter(superClass.substring(1)));
} else if (second == 'L') {
ValueType.Object itemType = (ValueType.Object) ValueType.parse(superClass.substring(1));
result = new SuperArrayFilter(this, getSuperClassFilter(itemType.getClassName()));
} else {
result = new ExactTypeFilter(superClass);
}
} else {
result = new SuperClassFilter(classSource, superClass);
}
superClassFilters.put(superClass, result);
}
return result;
} }
} }

View File

@ -321,7 +321,7 @@ class DependencyGraphBuilder {
private final Set<MethodReference> knownMethods = new HashSet<>(); private final Set<MethodReference> knownMethods = new HashSet<>();
private final BitSet knownTypes = new BitSet(); private final BitSet knownTypes = new BitSet();
private ExceptionConsumer exceptionConsumer; private ExceptionConsumer exceptionConsumer;
private SuperClassFilter filter; private DependencyTypeFilter filter;
VirtualCallConsumer(DependencyNode node, String filterClass, VirtualCallConsumer(DependencyNode node, String filterClass,
MethodDescriptor methodDesc, DependencyAnalyzer analyzer, DependencyNode[] parameters, MethodDescriptor methodDesc, DependencyAnalyzer analyzer, DependencyNode[] parameters,
@ -441,13 +441,27 @@ class DependencyGraphBuilder {
ClassReaderSource classSource = dependencyAnalyzer.getClassSource(); ClassReaderSource classSource = dependencyAnalyzer.getClassSource();
if (targetType instanceof ValueType.Object) { if (targetType instanceof ValueType.Object) {
String targetClsName = ((ValueType.Object) targetType).getClassName(); String targetClsName = ((ValueType.Object) targetType).getClassName();
final ClassReader targetClass = classSource.get(targetClsName); ClassReader targetClass = classSource.get(targetClsName);
if (targetClass != null && !(targetClass.getName().equals("java.lang.Object"))) { if (targetClass != null && !(targetClass.getName().equals("java.lang.Object"))) {
if (valueNode != null && receiverNode != null) { if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode, dependencyAnalyzer.getSuperClassFilter(targetClass.getName())); valueNode.connect(receiverNode, dependencyAnalyzer.getSuperClassFilter(targetClass.getName()));
} }
return; return;
} }
} else if (targetType instanceof ValueType.Array) {
ValueType itemType = targetType;
while (itemType instanceof ValueType.Array) {
itemType = ((ValueType.Array) itemType).getItemType();
}
if (itemType instanceof ValueType.Object) {
ClassReader targetClass = classSource.get(((ValueType.Object) itemType).getClassName());
if (targetClass == null) {
valueNode.connect(receiverNode);
return;
}
}
valueNode.connect(receiverNode, dependencyAnalyzer.getSuperClassFilter(targetType.toString()));
return;
} }
if (valueNode != null && receiverNode != null) { if (valueNode != null && receiverNode != null) {
valueNode.connect(receiverNode); valueNode.connect(receiverNode);

View File

@ -33,7 +33,7 @@ public class DependencyNode implements ValueDependencyInfo {
boolean locked; boolean locked;
MethodReference method; MethodReference method;
private ValueType typeFilter; private ValueType typeFilter;
private SuperClassFilter cachedTypeFilter; private DependencyTypeFilter cachedTypeFilter;
DependencyNode(DependencyAnalyzer dependencyAnalyzer, ValueType typeFilter) { DependencyNode(DependencyAnalyzer dependencyAnalyzer, ValueType typeFilter) {
this(dependencyAnalyzer, typeFilter, 0); this(dependencyAnalyzer, typeFilter, 0);

View File

@ -0,0 +1,29 @@
/*
* Copyright 2018 Alexey Andreev.
*
* 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 org.teavm.dependency;
class ExactTypeFilter implements DependencyTypeFilter {
String typeName;
ExactTypeFilter(String typeName) {
this.typeName = typeName;
}
@Override
public boolean match(DependencyType type) {
return typeName.equals(type.getName());
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2018 Alexey Andreev.
*
* 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 org.teavm.dependency;
import org.teavm.model.ValueType;
class SuperArrayFilter implements DependencyTypeFilter {
private DependencyAnalyzer analyzer;
private DependencyTypeFilter itemTypeFilter;
SuperArrayFilter(DependencyAnalyzer analyzer, DependencyTypeFilter itemTypeFilter) {
this.analyzer = analyzer;
this.itemTypeFilter = itemTypeFilter;
}
@Override
public boolean match(DependencyType type) {
if (!type.getName().startsWith("[")) {
return false;
}
String typeName = type.getName().substring(1);
ValueType valueType = ValueType.parseIfPossible(typeName);
if (valueType == null || valueType instanceof ValueType.Primitive) {
return false;
}
if (valueType instanceof ValueType.Object) {
typeName = ((ValueType.Object) valueType).getClassName();
}
return itemTypeFilter.match(analyzer.getType(typeName));
}
}

View File

@ -43,7 +43,7 @@ public class AnnotationDependencySupport extends AbstractDependencyListener {
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) { public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
if (method.getReference().getClassName().equals(Platform.class.getName()) if (method.getReference().getClassName().equals(Platform.class.getName())
&& method.getReference().getName().equals("getAnnotations")) { && method.getReference().getName().equals("getAnnotations")) {
method.getResult().propagate(agent.getType("[" + Annotation.class.getName())); method.getResult().propagate(agent.getType("[" + ValueType.parse(Annotation.class).toString()));
agent.linkMethod(new MethodReference(PlatformAnnotationProvider.class, "getAnnotations", agent.linkMethod(new MethodReference(PlatformAnnotationProvider.class, "getAnnotations",
Annotation[].class), location); Annotation[].class), location);
allClasses.addConsumer(type -> { allClasses.addConsumer(type -> {

View File

@ -51,6 +51,9 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin
case "getCurrentThread": case "getCurrentThread":
method.getResult().propagate(agent.getType("java.lang.Thread")); method.getResult().propagate(agent.getType("java.lang.Thread"));
break; break;
case "getEnumConstants":
method.getResult().propagate(agent.getType("[Ljava/lang/Enum;"));
break;
} }
} }