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.

There is a newer version: v20240317
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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
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.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.ArrayList;
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 JSTypeRegistry typeRegistry; private final String logFunction; RuntimeTypeCheck(AbstractCompiler compiler, @Nullable String logFunction) { this.compiler = compiler; this.typeRegistry = compiler.getTypeRegistry(); this.logFunction = logFunction; } @Override public void process(Node externs, Node root) { NodeTraversal.traverse(compiler, root, new AddMarkers(compiler)); NodeTraversal.traverse(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 NodeTraversal traversal; private AddMarkers(AbstractCompiler compiler) { this.compiler = compiler; } @Override public void visit(NodeTraversal t, Node node, Node unused) { this.traversal = t; @Nullable FunctionType funType = JSType.toMaybeFunctionType(node.getJSType()); switch (node.getToken()) { case FUNCTION: if (NodeUtil.isEs6Constructor(node)) { break; // "constructor" members are not constructors. } visitPossibleClassDeclaration( funType, findNodeToInsertAfter(node), this::addMarkerToFunction); break; case CLASS: visitPossibleClassDeclaration(funType, node.getChildAtIndex(2), this::addMarkerToClass); break; default: break; } } private interface MarkerInserter { Node insert(String markerName, @Nullable String className, Node insertionPoint); } private void visitPossibleClassDeclaration( @Nullable FunctionType funType, Node insertionPoint, MarkerInserter inserter) { // Validate the class type. if (funType == null || funType.getSource() == null || !funType.isConstructor()) { return; } @Nullable String className = NodeUtil.getName(funType.getSource()); // Assemble the marker names. Class marker first, then interfaces sorted aphabetically. ArrayList markerNames = new ArrayList<>(); for (ObjectType interfaceType : funType.getAllImplementedInterfaces()) { markerNames.add("implements__" + interfaceType.getReferenceName()); } markerNames.sort(Comparator.naturalOrder()); // Sort to ensure deterministic output. if (className != null) { // We can't generate markers for anonymous classes, but there's also no way to specify them // as a parameter type, so there will never be any checks for them either. markerNames.add(0, "instance_of__" + className); } // Insert the markers. for (String markerName : markerNames) { insertionPoint = inserter.insert(markerName, className, insertionPoint); } } /** * Adds a computed property method, with the name of the marker, to the class. * *

{@code
     * class C {
     *   ['instance_of__C']() {}
     * }
     * }
*/ private Node addMarkerToClass(String markerName, @Nullable String unused, Node classMembers) { Node function = IR.function(IR.name(""), IR.paramList(), IR.block()); Node member = IR.computedProp(IR.string(markerName), function); member.putBooleanProp(Node.COMPUTED_PROP_METHOD, true); classMembers.addChildToBack(member); compiler.reportChangeToEnclosingScope(member); compiler.reportChangeToChangeScope(function); NodeUtil.addFeatureToScript( traversal.getCurrentScript(), Feature.COMPUTED_PROPERTIES, compiler); return classMembers; } /** * Assigns a {@code true} prop, with the name of the marker. to the prototype of the class. * *
{@code
     * /** @constructor *\/
     * function C() { }
     *
     * C.prototype['instance_of__C'] = true;
     * }
*/ private Node addMarkerToFunction( String markerName, @Nullable String className, Node nodeToInsertAfter) { if (className == null) { // This can happen with anonymous classes declared with the type `Function`. return nodeToInsertAfter; } Node classNode = NodeUtil.newQName(compiler, className); Node assign = IR.exprResult( IR.assign( IR.getelem(IR.getprop(classNode, IR.string("prototype")), IR.string(markerName)), IR.trueNode())); nodeToInsertAfter.getParent().addChildAfter(assign, nodeToInsertAfter); compiler.reportChangeToEnclosingScope(assign); return assign; } /** * 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().isBlock()) { 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; } switch (n.getToken()) { case FUNCTION: visitFunction(n); break; case RETURN: case YIELD: visitTerminal(t, n); break; default: break; } } /** Insert checks for the parameters of the function. */ private void visitFunction(Node n) { Node block = n.getLastChild(); 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 paramName : paramNamesOf(NodeUtil.getFunctionParameters(n))) { checkState(paramName.isName(), paramName); Node checkNode = createCheckTypeCallNode(paramName.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); insertionPoint = checkNode; } } private void visitTerminal(NodeTraversal t, Node n) { Node function = t.getEnclosingFunction(); FunctionType funType = JSType.toMaybeFunctionType(function.getJSType()); if (funType == null) { return; } Node retValue = n.getFirstChild(); if (retValue == null) { return; } // Transform the documented return type of the function into the appropriate terminal type // based on any function or terminator modifiers. (e.g. `async`, `yield*`) JSType expectedTerminalType = funType.getReturnType(); if (function.isGeneratorFunction()) { expectedTerminalType = JsIterables.getElementType(expectedTerminalType, typeRegistry); } if (function.isAsyncFunction()) { expectedTerminalType = Promises.createAsyncReturnableType(typeRegistry, expectedTerminalType); } if (n.isYieldAll()) { expectedTerminalType = JsIterables.createIterableTypeOf(expectedTerminalType, typeRegistry); } Node checkNode = createCheckTypeCallNode(expectedTerminalType, 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) { final Collection alternates; if (type.isUnionType()) { alternates = new TreeSet<>(ALPHA); // Sorted to ensure deterministic output alternates.addAll(type.toMaybeUnionType().getAlternates()); } else { alternates = ImmutableList.of(type); } Node arrayNode = IR.arraylit(); 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; } } } /** * Returns the NAME parameter nodes of a FUNCTION. * *

This lookup abstracts over the other legal node types in a PARAM_LIST. It includes only * those nodes that declare bindings within the function body. */ private static ImmutableList paramNamesOf(Node paramList) { checkArgument(paramList.isParamList(), paramList); ImmutableList.Builder builder = ImmutableList.builder(); NodeUtil.getParamOrPatternNames(paramList, builder::add); return builder.build(); } private void addBoilerplateCode() { Node newNode = compiler.ensureLibraryInjected("runtime_type_check", false); if (newNode != null) { injectCustomLogFunction(newNode); } } @VisibleForTesting void injectCustomLogFunction(Node node) { if (logFunction == null) { return; } checkState( NodeUtil.isValidQualifiedName(compiler.getFeatureSet(), logFunction), "%s is not a valid qualified name", logFunction); Node logOverride = IR.exprResult( IR.assign( NodeUtil.newQName(compiler, "$jscomp.typecheck.log"), NodeUtil.newQName(compiler, logFunction))); checkState(node.getParent().isScript(), node.getParent()); node.getParent().addChildAfter(logOverride, node); compiler.reportChangeToEnclosingScope(node); } private Node jsCode(String prop) { return NodeUtil.newQName(compiler, "$jscomp.typecheck." + prop); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy