| /* |
| * Copyright 2014 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.jjs.impl; |
| |
| import com.google.gwt.core.ext.TreeLogger; |
| import com.google.gwt.core.ext.UnableToCompleteException; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JInterfaceType; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JVisitor; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsExpression; |
| import com.google.gwt.dev.js.ast.JsInvocation; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsNameRef; |
| import com.google.gwt.thirdparty.guava.common.collect.Maps; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Checks and throws errors for invalid JSNI constructs. |
| */ |
| public class JsniRestrictionChecker extends AbstractRestrictionChecker { |
| |
| public static void exec(TreeLogger logger, JProgram program) |
| throws UnableToCompleteException { |
| new JsniRestrictionChecker().checkProgram(logger, program); |
| } |
| |
| private void checkProgram(TreeLogger logger, final JProgram program) |
| throws UnableToCompleteException { |
| final Set<JDeclaredType> typesRequiringTrampolineDispatch = Sets.newHashSet(); |
| for (JDeclaredType type : program.getRepresentedAsNativeTypes()) { |
| collectAllSuperTypes(type, typesRequiringTrampolineDispatch); |
| } |
| new JVisitor() { |
| @Override |
| public boolean visit(JMethodBody x, Context ctx) { |
| // Skip non jsni methods. |
| return false; |
| } |
| |
| @Override |
| public boolean visit(final JsniMethodBody x, Context ctx) { |
| final Map<String, JsniMethodRef> methodsByJsniReference = Maps.newHashMap(); |
| for (JsniMethodRef ref : x.getJsniMethodRefs()) { |
| methodsByJsniReference.put(ref.getIdent(), ref); |
| } |
| if (methodsByJsniReference.isEmpty()) { |
| return false; |
| } |
| |
| // Examine the JS AST that represents the JSNI method body to check for devirtualizable |
| // methods references that are not directly called. |
| new JsModVisitor() { |
| @Override |
| public boolean visit(JsInvocation x, JsContext ctx) { |
| if (!(x.getQualifier() instanceof JsNameRef)) { |
| // If the invocation does not have a name as a qualifier (it might be an |
| // expression), the it is certainly not a JSNI method reference; but it might |
| // contain one so explore its subnodes the usual way. |
| return true; |
| } |
| JsNameRef ref = (JsNameRef) x.getQualifier(); |
| if (!ref.isJsniReference()) { |
| // The invocation is not to a JSNI method; but its subnodes might contain one |
| // hence explore them the usual way. |
| return true; |
| } |
| |
| // Skip the method JsNameRef but check the qualifier. |
| JsExpression methodQualifier = ref.getQualifier(); |
| if (methodQualifier != null) { |
| // Even if it is a direct call, there might be a reference in the qualifier. |
| accept(methodQualifier); |
| } |
| |
| // This is a direct call so if it was a JSNI reference to a devirtualized method |
| // it is safe, as it will be rewritten by {@see Devirtualizer}. |
| return false; |
| } |
| |
| @Override |
| public void endVisit(JsNameRef x, JsContext ctx) { |
| JsniMethodRef jsniMethodReference = methodsByJsniReference.get(x.getIdent()); |
| if (jsniMethodReference != null) { |
| // This is a JSNI reference that is not in a direct call, so check if it is valid. |
| checkJsniMethodReference(jsniMethodReference); |
| } |
| } |
| }.accept(x.getFunc()); |
| return false; |
| } |
| |
| private void checkJsniMethodReference(JsniMethodRef jsniMethodReference) { |
| JMethod method = jsniMethodReference.getTarget(); |
| JDeclaredType enclosingType = method.getEnclosingType(); |
| |
| if (isNonStaticJsoClassDispatch(method, enclosingType) |
| || isJsoInterface(enclosingType)) { |
| logError(jsniMethodReference, |
| "Method %s is implemented by a JSO and can only be used in calls " |
| + "within a JSNI method body.", |
| getDescription(method)); |
| } else if (program.isRepresentedAsNativeJsPrimitive(enclosingType) |
| && !method.isStatic() |
| && !method.isConstructor()) { |
| logError(jsniMethodReference, |
| "Method %s is implemented by devirtualized type %s JSO and can only be used in " |
| + "calls within a JSNI method body.", |
| getDescription(method), |
| getDescription(enclosingType)); |
| } else if (typesRequiringTrampolineDispatch.contains(enclosingType) |
| && !method.isStatic() |
| && !method.isConstructor()) { |
| logWarning(jsniMethodReference, |
| "Unsafe reference to method %s. Instance methods from %s should " |
| + "not be called on Boolean, Double, String, Array or JSO instances " |
| + "from within a JSNI method body.", |
| getDescription(method), |
| getDescription(enclosingType)); |
| } |
| } |
| |
| private boolean isJsoInterface(JDeclaredType type) { |
| return program.typeOracle.isSingleJsoImpl(type) |
| || program.typeOracle.isDualJsoInterface(type); |
| } |
| }.accept(program); |
| |
| boolean hasErrors = reportErrorsAndWarnings(logger); |
| if (hasErrors) { |
| throw new UnableToCompleteException(); |
| } |
| } |
| |
| private static void collectAllSuperTypes(JDeclaredType type, Set<JDeclaredType> allSuperTypes) { |
| if (type.getSuperClass() != null) { |
| allSuperTypes.add(type.getSuperClass()); |
| collectAllSuperTypes(type.getSuperClass(), allSuperTypes); |
| } |
| for (JInterfaceType interfaceType : type.getImplements()) { |
| allSuperTypes.add(interfaceType); |
| collectAllSuperTypes(interfaceType, allSuperTypes); |
| } |
| } |
| |
| private static boolean isNonStaticJsoClassDispatch(JMethod method, JDeclaredType enclosingType) { |
| return !method.isStatic() && enclosingType.isJsoType(); |
| } |
| |
| private JsniRestrictionChecker() { |
| } |
| } |