blob: e3eb08592ea39c387f04d02e7e7a5a79bec6fa09 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* 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 com.google.gwt.dev.javac;
import com.google.gwt.dev.jdt.SafeASTVisitor;
import com.google.gwt.dev.util.InstalledHelpInfo;
import com.google.gwt.dev.util.collect.Stack;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import java.util.HashMap;
import java.util.Map;
/**
* Check a compilation unit for violations of
* {@link com.google.gwt.core.client.JavaScriptObject JavaScriptObject} (JSO)
* restrictions. The restrictions are summarized in
* <code>jsoRestrictions.html</code>.
*
*
* Any violations found are attached as errors on the
* CompilationUnitDeclaration.
*
* @see <a
* href="http://code.google.com/p/google-web-toolkit/wiki/OverlayTypes">Overlay
* types design doc</a>
* @see jsoRestrictions.html
*/
public class JSORestrictionsChecker {
public static final String ERR_CONSTRUCTOR_WITH_PARAMETERS =
"Constructors must not have parameters in subclasses of JavaScriptObject";
public static final String ERR_INSTANCE_FIELD =
"Instance fields cannot be used in subclasses of JavaScriptObject";
public static final String ERR_INSTANCE_METHOD_NONFINAL =
"Instance methods must be 'final' in non-final subclasses of JavaScriptObject";
public static final String ERR_IS_NONSTATIC_NESTED =
"Nested classes must be 'static' if they extend JavaScriptObject";
public static final String ERR_NEW_JSO =
"'new' cannot be used to create instances of JavaScriptObject subclasses; "
+ "instances must originate in JavaScript";
public static final String ERR_NONEMPTY_CONSTRUCTOR =
"Constructors must be totally empty in subclasses of JavaScriptObject";
public static final String ERR_NONPROTECTED_CONSTRUCTOR =
"Constructors must be 'protected' in subclasses of JavaScriptObject";
public static final String ERR_OVERRIDDEN_METHOD =
"Methods cannot be overridden in JavaScriptObject subclasses";
public static final String ERR_JS_FUNCTION_ONLY_ALLOWED_ON_FUNCTIONAL_INTERFACE =
"@JsFunction is only allowed on functional interface";
private enum ClassState {
NORMAL, JSO
}
/**
* The order in which the checker will process types is undefined, so this
* type accumulates the information necessary for sanity-checking the JSO
* types.
*/
public static class CheckerState {
private final Map<String, String> interfacesToJsoImpls = new HashMap<String, String>();
public void addJsoInterface(TypeDeclaration jsoType,
CompilationUnitDeclaration cud, ReferenceBinding interf) {
String intfName = CharOperation.toString(interf.compoundName);
String alreadyImplementor = interfacesToJsoImpls.get(intfName);
String myName = CharOperation.toString(jsoType.binding.compoundName);
if (alreadyImplementor != null) {
String msg = errAlreadyImplemented(intfName, alreadyImplementor, myName);
errorOn(jsoType, cud, msg);
return;
}
interfacesToJsoImpls.put(intfName, myName);
}
}
private class JSORestrictionsVisitor extends SafeASTVisitor implements
ClassFileConstants {
private final Stack<ClassState> classStateStack = new Stack<ClassState>();
private final Stack<SourceTypeBinding> typeBindingStack = new Stack<SourceTypeBinding>();
@Override
public void endVisit(AllocationExpression exp, BlockScope scope) {
// In rare cases we might not be able to resolve the expression.
if (exp.type == null) {
return;
}
TypeBinding resolvedType = exp.resolvedType;
if (resolvedType == null) {
if (scope == null) {
return;
}
resolvedType = exp.type.resolveType(scope);
}
// Anywhere an allocation occurs is wrong.
if (JdtUtil.isJsoSubclass(resolvedType)) {
errorOn(exp, ERR_NEW_JSO);
}
}
@Override
public void endVisit(ConstructorDeclaration meth, ClassScope scope) {
if (!isJso()) {
return;
}
if ((meth.arguments != null) && (meth.arguments.length > 0)) {
errorOn(meth, ERR_CONSTRUCTOR_WITH_PARAMETERS);
}
if ((meth.modifiers & AccProtected) == 0) {
errorOn(meth, ERR_NONPROTECTED_CONSTRUCTOR);
}
if (meth.statements != null && meth.statements.length > 0) {
errorOn(meth, ERR_NONEMPTY_CONSTRUCTOR);
}
}
@Override
public void endVisit(FieldDeclaration field, MethodScope scope) {
if (!isJso()) {
return;
}
if (!field.isStatic()) {
errorOn(field, ERR_INSTANCE_FIELD);
}
}
@Override
public void endVisit(MethodDeclaration meth, ClassScope scope) {
if (!isJso()) {
return;
}
if ((meth.modifiers & (AccFinal | AccPrivate | AccStatic)) == 0) {
// The method's modifiers allow it to be overridden. Make
// one final check to see if the surrounding class is final.
if ((meth.scope == null) || !meth.scope.enclosingSourceType().isFinal()) {
errorOn(meth, ERR_INSTANCE_METHOD_NONFINAL);
}
}
// Should not have to check isStatic() here, but isOverriding() appears
// to be set for static methods.
if (!meth.isStatic()
&& (meth.binding != null && meth.binding.isOverriding())) {
errorOn(meth, ERR_OVERRIDDEN_METHOD);
}
}
@Override
public void endVisit(TypeDeclaration type, ClassScope scope) {
popState();
}
@Override
public void endVisit(TypeDeclaration type, CompilationUnitScope scope) {
popState();
}
@Override
public void endVisitValid(TypeDeclaration type, BlockScope scope) {
popState();
}
@Override
public boolean visit(TypeDeclaration type, ClassScope scope) {
pushState(type);
return true;
}
@Override
public boolean visit(TypeDeclaration type, CompilationUnitScope scope) {
pushState(type);
return true;
}
@Override
public boolean visitValid(TypeDeclaration type, BlockScope scope) {
pushState(type);
return true;
}
private void checkJsFunction(TypeDeclaration type, TypeBinding typeBinding) {
ReferenceBinding binding = (ReferenceBinding) typeBinding;
if (JdtUtil.getAnnotation(binding, "jsinterop.annotations.JsFunction") == null) {
return;
}
if (!binding.isFunctionalInterface(type.scope)) {
errorOn(type, ERR_JS_FUNCTION_ONLY_ALLOWED_ON_FUNCTIONAL_INTERFACE);
return;
}
}
private ClassState checkType(TypeDeclaration type) {
SourceTypeBinding binding = type.binding;
checkJsFunction(type, binding);
if (!JdtUtil.isJsoSubclass(binding)) {
return ClassState.NORMAL;
}
if (type.enclosingType != null && !binding.isStatic()) {
errorOn(type, ERR_IS_NONSTATIC_NESTED);
}
ReferenceBinding[] interfaces = binding.superInterfaces();
if (interfaces != null) {
for (ReferenceBinding interf : interfaces) {
if (interf.methods() == null) {
continue;
}
if (interf.methods().length > 0) {
// See if any of my superTypes implement it.
ReferenceBinding superclass = binding.superclass();
if (superclass == null
|| !superclass.implementsInterface(interf, true)) {
state.addJsoInterface(type, cud, interf);
}
}
}
}
return ClassState.JSO;
}
private boolean isJso() {
return classStateStack.peek() == ClassState.JSO;
}
private void popState() {
classStateStack.pop();
typeBindingStack.pop();
}
private void pushState(TypeDeclaration type) {
classStateStack.push(checkType(type));
typeBindingStack.push(type.binding);
}
}
/**
* Checks an entire
* {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration}.
*
*/
public static void check(CheckerState state, CompilationUnitDeclaration cud) {
JSORestrictionsChecker checker = new JSORestrictionsChecker(state, cud);
checker.check();
}
static String errAlreadyImplemented(String intfName, String impl1,
String impl2) {
return "Only one JavaScriptObject type may implement the methods of an "
+ "interface that declared methods. The interface (" + intfName
+ ") is implemented by both (" + impl1 + ") and (" + impl2 + ")";
}
private static void errorOn(ASTNode node, CompilationUnitDeclaration cud,
String error) {
GWTProblem.recordError(node, cud, error, new InstalledHelpInfo(
"jsoRestrictions.html"));
}
private final CompilationUnitDeclaration cud;
private final CheckerState state;
private JSORestrictionsChecker(CheckerState state,
CompilationUnitDeclaration cud) {
this.cud = cud;
this.state = state;
}
private void check() {
cud.traverse(new JSORestrictionsVisitor(), cud.scope);
}
private void errorOn(ASTNode node, String error) {
errorOn(node, cud, error);
}
}