google / closure-compiler

A JavaScript checker and optimizer.
https://developers.google.com/closure/compiler/
Apache License 2.0
7.4k stars 1.15k forks source link

Internal functions can capture implementation names by using $jscomp$ #3591

Open luisggpina opened 4 years ago

luisggpina commented 4 years ago

Input for SIMPLE_OPTIMIZATIONS:

function u_0(){ while (i_1){ i_1() };function i_1(i_1$jscomp$1, i_1){ } }

Error output:

Exception in thread "main" java.lang.RuntimeException: INTERNAL COMPILER ERROR.
Please report this problem.

Multiple entries with same key: i_1$jscomp$1=VOID 1 [length: 5] [source_file: id_000004.input] and i_1$jscomp$1=VOID 1 [length: 5] [source_file: id_000004.input]
  Node(CALL): id_000004.input:1:29
function u_0(){ while (i_1){ i_1() };function i_1(i_1$jscomp$1, i_1){ } }
  Parent(EXPR_RESULT): id_000004.input:1:29
function u_0(){ while (i_1){ i_1() };function i_1(i_1$jscomp$1, i_1){ } }

        at com.google.common.collect.ImmutableMap.conflictException(ImmutableMap.java:215)
        at com.google.common.collect.ImmutableMap.checkNoConflict(ImmutableMap.java:209)
        at com.google.common.collect.RegularImmutableMap.checkNoConflictInKeyBucket(RegularImmutableMap.java:147)
        at com.google.common.collect.RegularImmutableMap.fromEntryArray(RegularImmutableMap.java:110)
        at com.google.common.collect.ImmutableMap$Builder.build(ImmutableMap.java:393)
        at com.google.javascript.jscomp.FunctionArgumentInjector.getFunctionCallParameterMap(FunctionArgumentInjector.java:176)
        at com.google.javascript.jscomp.FunctionInjector.canInlineReferenceDirectly(FunctionInjector.java:935)
        at com.google.javascript.jscomp.FunctionInjector.canInlineReferenceToFunction(FunctionInjector.java:314)
        at com.google.javascript.jscomp.InlineFunctions$FindCandidatesReferences.maybeAddReferenceUsingMode(InlineFunctions.java:550)
        at com.google.javascript.jscomp.InlineFunctions$FindCandidatesReferences.maybeAddReference(InlineFunctions.java:519)
        at com.google.javascript.jscomp.InlineFunctions$FindCandidatesReferences.visitCallSite(InlineFunctions.java:508)
        at com.google.javascript.jscomp.InlineFunctions$CallVisitor.visit(InlineFunctions.java:445)
        at com.google.javascript.jscomp.InlineFunctions$FindCandidatesReferences.visit(InlineFunctions.java:500)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:909)
        at com.google.javascript.jscomp.NodeTraversal.traverseChildren(NodeTraversal.java:1037)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:905)
        at com.google.javascript.jscomp.NodeTraversal.traverseChildren(NodeTraversal.java:1037)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:902)
        at com.google.javascript.jscomp.NodeTraversal.traverseChildren(NodeTraversal.java:1037)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:902)
        at com.google.javascript.jscomp.NodeTraversal.traverseChildren(NodeTraversal.java:1037)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:902)
        at com.google.javascript.jscomp.NodeTraversal.traverseFunction(NodeTraversal.java:948)
        at com.google.javascript.jscomp.NodeTraversal.handleFunction(NodeTraversal.java:854)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:880)
        at com.google.javascript.jscomp.NodeTraversal.traverseChildren(NodeTraversal.java:1037)
        at com.google.javascript.jscomp.NodeTraversal.handleScript(NodeTraversal.java:842)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:877)
        at com.google.javascript.jscomp.NodeTraversal.traverseChildren(NodeTraversal.java:1037)
        at com.google.javascript.jscomp.NodeTraversal.traverseBranch(NodeTraversal.java:905)
        at com.google.javascript.jscomp.NodeTraversal.traverse(NodeTraversal.java:415)
        at com.google.javascript.jscomp.NodeTraversal.traverse(NodeTraversal.java:425)
        at com.google.javascript.jscomp.InlineFunctions.process(InlineFunctions.java:130)
        at com.google.javascript.jscomp.PhaseOptimizer$NamedPass.process(PhaseOptimizer.java:317)
        at com.google.javascript.jscomp.PhaseOptimizer$Loop.process(PhaseOptimizer.java:462)
        at com.google.javascript.jscomp.PhaseOptimizer.process(PhaseOptimizer.java:232)
        at com.google.javascript.jscomp.Compiler.performOptimizations(Compiler.java:2417)
        at com.google.javascript.jscomp.Compiler.lambda$stage2Passes$1(Compiler.java:802)
        at com.google.javascript.jscomp.CompilerExecutor.runInCompilerThread(CompilerExecutor.java:129)
        at com.google.javascript.jscomp.Compiler.runInCompilerThread(Compiler.java:829)
        at com.google.javascript.jscomp.Compiler.stage2Passes(Compiler.java:799)
        at com.google.javascript.jscomp.Compiler.compile(Compiler.java:689)
        at com.google.javascript.jscomp.Compiler.compile(Compiler.java:656)
        at edu.berkeley.cs.jqf.examples.closure.CompilerTest.main(CompilerTest.java:68)
Caused by: java.lang.IllegalArgumentException: Multiple entries with same key: i_1$jscomp$1=VOID 1 [length: 5] [source_file: id_000004.input] and i_1$jscomp$1=VOID 1 [length: 5] [source_file: id_000004.input]
        ... 44 more

Affects: v20200405

Reproduce by passing a file with the input to this class:

import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

import static org.junit.Assume.assumeTrue;

public class Compiler {

    static {
        java.util.logging.LogManager.getLogManager().reset();
    }

    // Compiler, options, and predefined JS environment
    private com.google.javascript.jscomp.Compiler compiler = new com.google.javascript.jscomp.Compiler(new PrintStream(new ByteArrayOutputStream(), false));
    private CompilerOptions options = new CompilerOptions();
    private SourceFile externs = SourceFile.fromCode("externs", "");

    public void initCompiler() {
        // Don't use threads
        compiler.disableThreads();
        // Don't print things
        options.setPrintConfig(false);
        // Enable all safe optimizations
        CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);
    }

    /** Compiles an input and returns its result */
    private Result compile(SourceFile input) {
        return compiler.compile(externs, input, options);
    }

    public static void main(String[] args) {
        Compiler t = new Compiler();
        t.compiler = new com.google.javascript.jscomp.Compiler(System.out);
        t.initCompiler();
        t.compiler.compile(t.externs, SourceFile.fromFile(args[0]), t.options);
    }
}

Compiler options (default options):

aggressiveFusion=false
 aliasableStrings=[]
 aliasAllStrings=false
 aliasHandler=com.google.javascript.jscomp.CompilerOptions$NullAliasTransformationHandler@5bb21b69
 aliasStringsBlacklist=
 allowHotswapReplaceScript=false
 ambiguateProperties=false
 angularPass=false
 anonymousFunctionNaming=OFF
 assumeClosuresOnlyCaptureReferences=false
 assumeGettersArePure=true
 assumeStrictThis=false
 browserResolverPrefixReplacements={}
 brokenClosureRequiresLevel=ERROR
 checkDeterminism=false
 checkGlobalNamesLevel=OFF
 checkGlobalThisLevel=OFF
 checkMissingGetCssNameLevel=OFF
 checksOnly=false
 checkSuspiciousCode=false
 checkSymbols=false
 checkTypes=false
 closurePass=true
 coalesceVariableNames=true
 collapseAnonymousFunctions=false
 collapseObjectLiterals=true
 collapseProperties=NONE
 collapseVariableDeclarations=true
 colorizeErrorOutput=false
 computeFunctionSideEffects=false
 conformanceConfigs=[]
 conformanceRemoveRegexFromPath=Optional.of(^((.*/)?google3/)?((^/)?(blaze|bazel)-out/[^/]+/bin/)?)
 continueAfterErrors=false
 convertToDottedProperties=true
 crossChunkCodeMotion=false
 crossChunkCodeMotionNoStubMethods=false
 crossChunkMethodMotion=false
 dartPass=false
 deadAssignmentElimination=true
 declaredGlobalExternsOnWindow=true
 defineReplacements={}
 dependencyOptions=DependencyOptions{mode=SORT_ONLY
 entryPoints=[]}
 devirtualizeMethods=false
 devMode=OFF
 disambiguatePrivateProperties=false
 disambiguateProperties=false
 enableModuleRewriting=true
 enforceAccessControlCodingConventions=false
 environment=BROWSER
 errorFormat=SINGLELINE
 es6ModuleTranspilation=COMPILE
 exportLocalPropertyDefinitions=false
 exportTestFunctions=false
 externExports=false
 extractPrototypeMemberDeclarations=OFF
 extraSmartNameRemoval=false
 filesToPrintAfterEachPassRegexList=[]
 flowSensitiveInlineVariables=false
 foldConstants=true
 forceLibraryInjection=[]
 gatherCssNames=false
 generateExports=false
 generatePseudoNames=false
 generateTypedExterns=false
 idGenerators={}
 incrementalCheckMode=OFF
 inferConsts=true
 inferTypes=false
 inlineConstantVars=false
 inlineFunctionsLevel=LOCAL_ONLY
 inlineGetters=false
 inlineLocalVariables=true
 inlineProperties=false
 inlineVariables=false
 inputDelimiter=// Input %num%
 inputSourceMaps={}
 instrumentForCoverage=false
 instrumentForCoverageOnly=false
 instrumentBranchCoverage=false
 isolatePolyfills=false
 j2clMinifierEnabled=true
 j2clPassMode=AUTO
 labelRenaming=true
 languageIn=ECMASCRIPT_2019
 languageOutIsDefaultStrict=Optional.absent()
 legacyCodeCompile=false
 lineBreak=false
 lineLengthThreshold=500
 markAsCompiled=false
 maxFunctionSizeAfterInlining=-1
 moduleRoots=[./]
 chunksToPrintAfterEachPassRegexList=[]
 moveFunctionDeclarations=false
 nameGenerator=com.google.javascript.jscomp.DefaultNameGenerator@32464a14
 optimizeArgumentsArray=true
 optimizeCalls=false
 outputFeatureSet=Optional.absent()
 outputJs=NORMAL
 outputJsStringUsage=false
 parentChunkCanSeeSymbolsDeclaredInChildren=false
 parseJsDocDocumentation=TYPES_ONLY
 pathEscaper=ESCAPE
 polymerExportPolicy=LEGACY
 preferLineBreakAtEndOfFile=false
 preferSingleQuotes=false
 preferStableNames=false
 preserveDetailedSourceInfo=false
 preserveGoogProvidesAndRequires=false
 preserveTypeAnnotations=false
 prettyPrint=false
 preventLibraryInjection=false
 printConfig=true
 printInputDelimiter=false
 printSourceAfterEachPass=false
 processCommonJSModules=false
 propertyInvalidationErrors={}
 propertyRenaming=OFF
 protectHiddenSideEffects=true
 quoteKeywordProperties=false
 removeAbstractMethods=false
 removeClosureAsserts=false
 removeJ2clAsserts=true
 removeDeadCode=true
 removeUnusedClassProperties=false
 removeUnusedConstructorProperties=false
 removeUnusedLocalVars=true
 removeUnusedPrototypeProperties=false
 removeUnusedPrototypePropertiesInExterns=false
 removeUnusedVars=false
 renamePrefixNamespaceAssumeCrossChunkNames=false
 replaceIdGenerators=false
 replaceMessagesWithChromeI18n=false
 replaceStringsFunctionDescriptions=[]
 replaceStringsPlaceholderToken=
 replaceStringsReservedStrings=[]
 reserveRawExports=false
 rewriteFunctionExpressions=false
 rewritePolyfills=false
 runtimeTypeCheck=false
 shadowVariables=true
 rewriteModulesBeforeTypechecking=true
 skipNonTranspilationPasses=false
 smartNameRemoval=false
 sourceMapDetailLevel=ALL
 sourceMapFormat=DEFAULT
 sourceMapLocationMappings=[]
 stripNamePrefixes=[]
 stripNameSuffixes=[]
 stripTypePrefixes=[]
 stripTypes=[]
 summaryDetailLevel=1
 tracer=OFF
 transformAMDToCJSModules=false
 trustedStrings=false
 tweakProcessing=OFF
 tweakReplacements={}
 emitUseStrict=Optional.absent()
 useTypesForLocalOptimization=false
 variableRenaming=LOCAL
 warningsGuard=DiagnosticGroup<checkVars>(OFF)
 DiagnosticGroup<es5Strict>(ERROR)
 DiagnosticGroup<boundedGenerics>(OFF)
 com.google.javascript.jscomp.DiagnosticGroup@4e4aea35(OFF)
 wrapGoogModulesForWhitespaceOnly=true
luisggpina commented 4 years ago

Shorter input that shows what's going on more clearly:

function outer(){
    inner("a","b");
    function inner(inner$jscomp$1, inner){ }
}

Reproduce here: https://closure-compiler-debugger.appspot.com/#input0%3Dfunction%2520outer()%257B%250A%2509inner(%2522a%2522%252C%2522b%2522)%253B%250A%2509function%2520inner(inner%2524jscomp%25241%252C%2520inner)%257B%2520%257D%250A%257D%26input1%26conformanceConfig%26externs%26refasterjs-template%26INLINE_FUNCTIONS%3Dtrue%26CLOSURE_PASS%3Dtrue%26NAME_ANONYMOUS_FUNCTIONS%3Dtrue%26PRESERVE_TYPE_ANNOTATIONS%3Dtrue%26PRETTY_PRINT%3Dtrue

concavelenz commented 4 years ago

Why is this a problem for you?

luisggpina commented 4 years ago

We found this crashing input using a fuzzing tool that generates inputs automatically which may result in a crash. In this case, our tool found such an input: A piece of JavaScript that results in the compiler crashing with a RuntimeException, prompting the compiler to encourage a bug report.

Our tool only generates valid JavaScript (as per the JavaScript grammar), so this is a bug as the compiler should reject the input instead of crashing.