All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.RuntimeTypeCheck Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs. This binary checks for style issues such as incorrect or missing JSDoc usage, and missing goog.require() statements. It does not do more advanced checks such as typechecking.

There is a newer version: v20200830
Show newest version
/*
 * Copyright 2010 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.StaticSourceFile;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Collection;
import java.util.Comparator;
import java.util.TreeSet;
import javax.annotation.Nullable;

/**
 * Inserts run-time type assertions.
 *
 * 

We add markers to user-defined interfaces and classes in order to check if * an object conforms to that type. * *

For each function, we insert a run-time type assertion for each parameter * and return value for which the compiler has a type. * *

The JavaScript code which implements the type assertions is in * js/runtime-type-check.js. * */ class RuntimeTypeCheck implements CompilerPass { private static final Comparator ALPHA = new Comparator() { @Override public int compare(JSType t1, JSType t2) { return getName(t1).compareTo(getName(t2)); } private String getName(JSType type) { if (type.isInstanceType()) { return ((ObjectType) type).getReferenceName(); } else if (type.isNullType() || type.isBooleanValueType() || type.isNumberValueType() || type.isStringValueType() || type.isVoidType()) { return type.toString(); } else { // Type unchecked at runtime, so we don't care about the sorting order. return ""; } } }; private final AbstractCompiler compiler; private final String logFunction; RuntimeTypeCheck(AbstractCompiler compiler, @Nullable String logFunction) { this.compiler = compiler; this.logFunction = logFunction; } @Override public void process(Node externs, Node root) { NodeTraversal.traverseEs6(compiler, root, new AddMarkers(compiler)); NodeTraversal.traverseEs6(compiler, root, new AddChecks()); addBoilerplateCode(); new Normalize(compiler, false).process(externs, root); } /** * Inserts marker properties for user-defined interfaces and classes. * *

For example, for a class C, we add * {@code C.prototype['instance_of__C']}, and for each interface I it * implements , we add {@code C.prototype['implements__I']}. * *

Since interfaces are not a run-time JS concept, we use these markers to * recognize an interface implementation at runtime. We also use markers for * user-defined classes, so that we can easily recognize them independently of * which module they are defined in and whether the module is loaded. */ private static class AddMarkers extends NodeTraversal.AbstractPostOrderCallback { private final AbstractCompiler compiler; private AddMarkers(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (n.isFunction()) { visitFunction(n); } } private void visitFunction(Node n) { FunctionType funType = n.getJSType().toMaybeFunctionType(); if (funType != null && !funType.isConstructor()) { return; } Node nodeToInsertAfter = findNodeToInsertAfter(n); nodeToInsertAfter = addMarker(funType, nodeToInsertAfter, null); TreeSet stuff = new TreeSet<>(ALPHA); Iterables.addAll(stuff, funType.getAllImplementedInterfaces()); for (ObjectType interfaceType : stuff) { nodeToInsertAfter = addMarker(funType, nodeToInsertAfter, interfaceType); } } private Node addMarker( FunctionType funType, Node nodeToInsertAfter, @Nullable ObjectType interfaceType) { if (funType.getSource() == null) { return nodeToInsertAfter; } String className = NodeUtil.getName(funType.getSource()); // This can happen with anonymous classes declared with the type // {@code Function}. if (className == null) { return nodeToInsertAfter; } Node classNode = NodeUtil.newQName( compiler, className); Node marker = IR.string( interfaceType == null ? "instance_of__" + className : "implements__" + interfaceType.getReferenceName()); Node assign = IR.exprResult(IR.assign( IR.getelem( IR.getprop( classNode, IR.string("prototype")), marker), IR.trueNode())); nodeToInsertAfter.getParent().addChildAfter(assign, nodeToInsertAfter); compiler.reportChangeToEnclosingScope(assign); nodeToInsertAfter = assign; return nodeToInsertAfter; } /** * Find the node to insert the markers after. Typically, this node * corresponds to the constructor declaration, but we want to skip any of * the white-listed function calls. * * @param n the constructor function node * @return the node to insert after */ private Node findNodeToInsertAfter(Node n) { Node nodeToInsertAfter = findEnclosingConstructorDeclaration(n); Node next = nodeToInsertAfter.getNext(); while (next != null && isClassDefiningCall(next)) { nodeToInsertAfter = next; next = nodeToInsertAfter.getNext(); } return nodeToInsertAfter; } private static Node findEnclosingConstructorDeclaration(Node n) { while (!n.getParent().isScript() && !n.getParent().isNormalBlock()) { n = n.getParent(); } return n; } private boolean isClassDefiningCall(Node next) { return NodeUtil.isExprCall(next) && compiler.getCodingConvention().getClassesDefinedByCall( next.getFirstChild()) != null; } } /** * Insert calls to the run-time type checking function {@code checkType}, which * takes an expression to check and a list of checkers (one of which must * match). It returns the expression back to facilitate checking of return * values. We have checkers for value types, class types (user-defined and * externed), and interface types. */ private class AddChecks extends NodeTraversal.AbstractPostOrderCallback { private AddChecks() { } @Override public void visit(NodeTraversal t, Node n, Node parent) { if (NodeUtil.isInSyntheticScript(n)) { return; } if (n.isFunction()) { visitFunction(n); } else if (n.isReturn()) { visitReturn(t, n); } } /** * Insert checks for the parameters of the function. */ private void visitFunction(Node n) { FunctionType funType = JSType.toMaybeFunctionType(n.getJSType()); Node block = n.getLastChild(); Node paramName = NodeUtil.getFunctionParameters(n).getFirstChild(); Node insertionPoint = null; // To satisfy normalization constraints, the type checking must be // added after any inner function declarations. for (Node next = block.getFirstChild(); next != null && NodeUtil.isFunctionDeclaration(next); next = next.getNext()) { insertionPoint = next; } for (Node paramType : funType.getParameters()) { // Can this ever happen? if (paramName == null) { return; } Node checkNode = createCheckTypeCallNode( paramType.getJSType(), paramName.cloneTree()); if (checkNode == null) { // We don't know how to check this parameter type. paramName = paramName.getNext(); continue; } checkNode = IR.exprResult(checkNode); if (insertionPoint == null) { block.addChildToFront(checkNode); } else { block.addChildAfter(checkNode, insertionPoint); } compiler.reportChangeToEnclosingScope(block); paramName = paramName.getNext(); insertionPoint = checkNode; } } private void visitReturn(NodeTraversal t, Node n) { Node function = t.getEnclosingFunction(); FunctionType funType = function.getJSType().toMaybeFunctionType(); Node retValue = n.getFirstChild(); if (retValue == null) { return; } Node checkNode = createCheckTypeCallNode( funType.getReturnType(), retValue.cloneTree()); if (checkNode == null) { return; } n.replaceChild(retValue, checkNode); t.reportCodeChange(); } /** * Creates a function call to check that the given expression matches the * given type at runtime. * *

For example, if the type is {@code (string|Foo)}, the function call is * {@code checkType(expr, [valueChecker('string'), classChecker('Foo')])}. * * @return the function call node or {@code null} if the type is not checked */ private Node createCheckTypeCallNode(JSType type, Node expr) { Node arrayNode = IR.arraylit(); Collection alternates; if (type.isUnionType()) { alternates = new TreeSet<>(ALPHA); alternates.addAll(type.toMaybeUnionType().getAlternates()); } else { alternates = ImmutableList.of(type); } for (JSType alternate : alternates) { Node checkerNode = createCheckerNode(alternate); if (checkerNode == null) { return null; } arrayNode.addChildToBack(checkerNode); } return IR.call(jsCode("checkType"), expr, arrayNode); } /** * Creates a node which evaluates to a checker for the given type (which * must not be a union). We have checkers for value types, classes and * interfaces. * * @return the checker node or {@code null} if the type is not checked */ private Node createCheckerNode(JSType type) { if (type.isNullType()) { return jsCode("nullChecker"); } else if (type.isBooleanValueType() || type.isNumberValueType() || type.isStringValueType() || type.isVoidType()) { return IR.call( jsCode("valueChecker"), IR.string(type.toString())); } else if (type.isInstanceType()) { ObjectType objType = (ObjectType) type; String refName = objType.getReferenceName(); if (refName.equals("Object")) { return jsCode("objectChecker"); } StaticSourceFile sourceFile = NodeUtil.getSourceFile(objType.getConstructor().getSource()); if (sourceFile == null || sourceFile.isExtern()) { return IR.call( jsCode("externClassChecker"), IR.string(refName)); } return IR.call( jsCode(objType.getConstructor().isInterface() ? "interfaceChecker" : "classChecker"), IR.string(refName)); } else if (type.isFunctionType()) { return IR.call(jsCode("valueChecker"), IR.string("function")); } else { // We don't check this type (e.g. unknown & all types). return null; } } } private void addBoilerplateCode() { Node newNode = compiler.ensureLibraryInjected("runtime_type_check", false); if (newNode != null && logFunction != null) { // Inject the custom log function. Node logOverride = IR.exprResult( IR.assign( NodeUtil.newQName( compiler, "$jscomp.typecheck.log"), NodeUtil.newQName( compiler, logFunction))); newNode.getParent().addChildAfter(logOverride, newNode); compiler.reportChangeToEnclosingScope(newNode); } } private Node jsCode(String prop) { return NodeUtil.newQName( compiler, "$jscomp.typecheck." + prop); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy