Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
BEGIN LICENSE BLOCK ***** Version: EPL 1.0/GPL 2.0/LGPL 2.1 The contents of this file are subject to the Eclipse Public License Version 1.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.eclipse.org/legal/epl-v10.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Copyright (C) 2006-2008 Charles O Nutter <headius@headius.com> Copyright (C) 2008 Thomas E Enebo <enebo@acm.org> Alternatively, the contents of this file may be used under the terms of either of the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the LGPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of either the GPL or the LGPL, and not to allow others to use your version of this file under the terms of the EPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL or the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the EPL, the GPL or the LGPL. END LICENSE BLOCK ***
 
 package org.jruby.compiler;
 
 
 import java.io.File;
 import java.util.Set;
 import org.jruby.Ruby;
 import static org.jruby.compiler.impl.StandardASMCompiler.ARGS_INDEX;
 import static org.jruby.compiler.impl.StandardASMCompiler.SELF_INDEX;
 import static org.jruby.compiler.impl.StandardASMCompiler.THIS;
 import static org.jruby.compiler.impl.StandardASMCompiler.THREADCONTEXT_INDEX;
 import static org.jruby.compiler.impl.StandardASMCompiler.getMethodSignature;
 import static org.jruby.compiler.impl.StandardASMCompiler.getStaticMethodSignature;
 import static org.jruby.runtime.BlockBody.getArgumentTypeWackyHack;
 import  org.objectweb.asm.ClassReader;
 import  org.objectweb.asm.Opcodes;
 import  org.objectweb.asm.util.TraceClassVisitor;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
 
 public class JITCompiler implements JITCompilerMBean {
     private static final Logger LOG = LoggerFactory.getLogger("JITCompiler");
     
    public static final boolean USE_CACHE = true;
    public static final String RUBY_JIT_PREFIX = "rubyjit";
    public static final String CLASS_METHOD_DELIMITER = "$$";
    public static class JITCounts {
        private final AtomicLong compiledCount = new AtomicLong(0);
        private final AtomicLong successCount = new AtomicLong(0);
        private final AtomicLong failCount = new AtomicLong(0);
        private final AtomicLong abandonCount = new AtomicLong(0);
        private final AtomicLong compileTime = new AtomicLong(0);
        private final AtomicLong averageCompileTime = new AtomicLong(0);
        private final AtomicLong codeSize = new AtomicLong(0);
        private final AtomicLong averageCodeSize = new AtomicLong(0);
        private final AtomicLong largestCodeSize = new AtomicLong(0);
    }
    private final JITCounts counts = new JITCounts();
    private final ExecutorService executor;
    
    private final Ruby runtime;
    private final RubyInstanceConfig config;
    
    public JITCompiler(Ruby runtime) {
        this. = runtime;
        this. = runtime.getInstanceConfig();
        this. = new ThreadPoolExecutor(
                2, // always two threads
                2,
                0, // never stop
                .,
                new LinkedBlockingQueue<Runnable>(),
                new DaemonThreadFactory("Ruby-" + runtime.getRuntimeNumber() + "-JIT".));
        
        runtime.getBeanManager().register(this);
    }
    public long getSuccessCount() {
        return ..get();
    }
    public long getCompileCount() {
        return ..get();
    }
    public long getFailCount() {
        return ..get();
    }
    public long getCompileTime() {
        return ..get() / 1000;
    }
    public long getAbandonCount() {
        return ..get();
    }
    
    public long getCodeSize() {
        return ..get();
    }
    
    public long getAverageCodeSize() {
        return ..get();
    }
    
    public long getAverageCompileTime() {
        return ..get() / 1000;
    }
    
    public long getLargestCodeSize() {
        return ..get();
    }
    public void tryJIT(DefaultMethod methodThreadContext contextString classNameString methodName) {
        if (!.getCompileMode().shouldJIT()) return;
        
        if (method.incrementCallCount() < .getJitThreshold()) return;
        
        jitThresholdReached(methodcontextclassNamemethodName);
    }
    public void tearDown() {
        if ( != null) {
            try {
                .shutdown();
            } catch (SecurityException se) {
                // ignore, can't shut down executor
            }
        }
    }
    
    private void jitThresholdReached(final DefaultMethod methodfinal RubyInstanceConfig configThreadContext contextfinal String classNamefinal String methodName) {
        // Disable any other jit tasks from entering queue
        method.setCallCount(-1);
        final Ruby runtime = context.runtime;
        
        Runnable jitTask = new JITTask(classNamemethodmethodName);
        // if background JIT is enabled and threshold is > 0 and we have an executor...
        if (config.getJitBackground() &&
                config.getJitThreshold() > 0 &&
                 != null) {
            // JIT in background
            try {
                .submit(jitTask);
            } catch (RejectedExecutionException ree) {
                // failed to submit, just run it directly
                jitTask.run();
            }
        } else {
            // just run directly
            jitTask.run();
        }
    }
    
    private class JITTask implements Runnable {
        private final String className;
        private final DefaultMethod method;
        private final String methodName;
        
        public JITTask(String classNameDefaultMethod methodString methodName) {
            this. = className;
            this. = method;
            this. = methodName;
        }
        
        public void run() {
            try {
                // The cache is full. Abandon JIT for this method and bail out.
                ClassCache classCache = .getClassCache();
                if (classCache.isFull()) {
                    ..incrementAndGet();
                    return;
                }
                // Check if the method has been explicitly excluded
                if (.getExcludedMethods().size() > 0) {
                    String excludeModuleName = ;
                    if (.getImplementationClass().isSingleton()) {
                        IRubyObject possibleRealClass = ((MetaClass.getImplementationClass()).getAttached();
                        if (possibleRealClass instanceof RubyModule) {
                            excludeModuleName = "Meta:" + ((RubyModulepossibleRealClass).getName();
                        }
                    }
                    if ((.getExcludedMethods().contains(excludeModuleName)
                            || .getExcludedMethods().contains(excludeModuleName + "#" + )
                            || .getExcludedMethods().contains())) {
                        .setCallCount(-1);
                        log("skipping method: " + excludeModuleName + "#" + );
                        return;
                    }
                }
                String key = SexpMaker.sha1(.getArgsNode(), .getBodyNode());
                JITClassGenerator generator = new JITClassGenerator(key);
                Class<ScriptsourceClass = (Class<Script>) .getClassCache().cacheClassByKey(generator.digestStringgenerator);
                if (sourceClass == null) {
                    // class could not be found nor generated; give up on JIT and bail out
                    ..incrementAndGet();
                    return;
                }
                // successfully got back a jitted method
                ..incrementAndGet();
                // finally, grab the script
                Script jitCompiledScript = sourceClass.newInstance();
                // set root scope
                jitCompiledScript.setRootScope(.getStaticScope());
                // add to the jitted methods set
                Set<ScriptjittedMethods = .getJittedMethods();
                jittedMethods.add(jitCompiledScript);
                // logEvery n methods based on configuration
                if (.getJitLogEvery() > 0) {
                    int methodCount = jittedMethods.size();
                    if (methodCount % .getJitLogEvery() == 0) {
                        log("live compiled methods: " + methodCount);
                    }
                }
                if (.isJitLogging()) {
                    log( + "." + "done jitting");
                }
                .switchToJitted(jitCompiledScriptgenerator.callConfig());
                return;
            } catch (Throwable t) {
                if (.getDebug().isTrue()) {
                    t.printStackTrace();
                }
                if (.isJitLoggingVerbose()) {
                    log( + "." + "could not compile"t.getMessage());
                }
                ..incrementAndGet();
                return;
            }
        }
    }
    public static String getHashForString(String str) {
        return getHashForBytes(RubyEncoding.encodeUTF8(str));
    }
    public static String getHashForBytes(byte[] bytes) {
        try {
            MessageDigest sha1 = MessageDigest.getInstance("SHA1");
            sha1.update(bytes);
            byte[] digest = sha1.digest();
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < digest.lengthi++) {
                builder.append(Integer.toString( ( digest[i] & 0xff ) + 0x100, 16).substring( 1 ));
            }
            return builder.toString().toUpperCase(.);
        } catch (NoSuchAlgorithmException nsae) {
            throw new RuntimeException(nsae);
        }
    }
    public static void saveToCodeCache(Ruby rubybyte[] bytecodeString packageNameFile cachedClassFile) {
        String codeCache = .;
        File codeCacheDir = new File(codeCache);
        if (!codeCacheDir.exists()) {
            ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " does not exist");
        } else if (!codeCacheDir.isDirectory()) {
            ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " is not a directory");
        } else if (!codeCacheDir.canWrite()) {
            ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " is not writable");
        } else {
            if (!new File(codeCachepackageName).isDirectory()) {
                boolean createdDirs = new File(codeCachepackageName).mkdirs();
                if (!createdDirs) {
                    ruby.getWarnings().warn("could not create JIT cache dir: " + new File(codeCachepackageName));
                }
            }
            // write to code cache
            FileOutputStream fos = null;
            try {
                if (..info("writing jitted code to to " + cachedClassFile);
                fos = new FileOutputStream(cachedClassFile);
                fos.write(bytecode);
            } catch (Exception e) {
                e.printStackTrace();
                // ignore
            } finally {
                try {fos.close();} catch (Exception e) {}
            }
        }
    }
    
    public static class JITClassGenerator implements ClassCache.ClassGenerator {
        public JITClassGenerator(String classNameString methodNameString keyRuby rubyDefaultMethod methodJITCounts counts) {
            this. = .;
            if (. == Opcodes.V1_7 || ..load() == true) {
                // Some versions of Java 7 seems to have a bug that leaks definitions across cousin classloaders
                // so we force the class name to be unique to this runtime.
                // Also, invokedynamic forces us to make jitted code unique to each runtime, since the call sites cache
                // at class level rather than at our runtime level. This makes it impossible to share jitted code
                // across runtimes.
                
                 = key + Math.abs(ruby.hashCode());
            } else {
                 = key;
            }
            this. =  + "/" + className.replace('.''/') +  + JavaNameMangler.mangleMethodName(methodName) + "_" + ;
            this. = this..replaceAll("/"".");
            this. = method.getBodyNode();
            this. = method.getArgsNode();
            this. = methodName;
             = calculateFilename();
             = method.getStaticScope();
             = new StandardASMCompiler(this.);
            this. = ruby;
            this. = counts;
        }
        
        @SuppressWarnings("unchecked")
        protected void compile() {
            if ( != nullreturn;
            
            // check if we have a cached compiled version on disk
            String codeCache = .;
            File cachedClassFile = new File(codeCache + "/" +  + ".class");
            if (codeCache != null &&
                    cachedClassFile.exists()) {
                FileInputStream fis = null;
                try {
                    if (..info("loading cached code from: " + cachedClassFile);
                    fis = new FileInputStream(cachedClassFile);
                     = new byte[(int)fis.getChannel().size()];
                    fis.read();
                     = new ClassReader().getClassName();
                    return;
                } catch (Exception e) {
                    // ignore and proceed to compile
                } finally {
                    try {fis.close();} catch (Exception e) {}
                }
            }
            
            // Time the compilation
            long start = System.nanoTime();
            .startScript();
            final ASTCompiler compiler = .getInstanceConfig().newCompiler();
            CompilerCallback args = new CompilerCallback() {
                public void call(BodyCompiler context) {
                    compiler.compileArgs(contexttrue);
                }
            };
            ASTInspector inspector = new ASTInspector();
            if (.getInstanceConfig().isJitDumping()) {
                inspector = new ASTInspector(true);
            }
            // check args first, since body inspection can depend on args
            inspector.inspect();
            inspector.inspect();
            BodyCompiler methodCompiler;
            if ( != null) {
                // we have a body, do a full-on method
                methodCompiler = .startFileMethod(argsinspector);
                compiler.compileBody(methodCompiler,true);
            } else {
                // If we don't have a body, check for required or opt args
                // if opt args, they could have side effects
                // if required args, need to raise errors if too few args passed
                // otherwise, method does nothing, make it a nop
                if ( != null && (.getRequiredArgsCount() > 0 || .getOptionalArgsCount() > 0)) {
                    methodCompiler = .startFileMethod(argsinspector);
                    methodCompiler.loadNil();
                } else {
                    methodCompiler = .startFileMethod(nullinspector);
                    methodCompiler.loadNil();
                     = .;
                }
            }
            methodCompiler.endBody();
            .endScript(falsefalse);
            
            // if we haven't already decided on a do-nothing call
            if ( == null) {
                 = inspector.getCallConfig();
            }
            
             = .getClassByteArray();
            if (.getInstanceConfig().isJitDumping()) {
                TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(.));
                new ClassReader().accept(tcv, 0);
            }
            
            if (. > .getInstanceConfig().getJitMaxSize()) {
                 = null;
                throw new NotCompilableException(
                        "JITed method size exceeds configured max of " +
                        .getInstanceConfig().getJitMaxSize());
            }
            if (codeCache != null) {
                JITCompiler.saveToCodeCache(cachedClassFile);
            }
            
            ..incrementAndGet();
            ..addAndGet(System.nanoTime() - start);
            ..addAndGet(.);
            synchronized () {
                if (..get() < .) {
                    ..set(.);
                }
            }
        }
        public void generate() {
            compile();
        }
        
        public byte[] bytecode() {
            return ;
        }
        public String name() {
            return ;
        }
        
        public CallConfiguration callConfig() {
            compile();
            return ;
        }
        @Override
        public String toString() {
            return  + "() at " + .getPosition().getFile() + ":" + .getPosition().getLine();
        }
        
        private final StandardASMCompiler asmCompiler;
        private final StaticScope staticScope;
        private final Node bodyNode;
        private final ArgsNode argsNode;
        private final Ruby ruby;
        private final String packageName;
        private final String className;
        private final String filename;
        private final String methodName;
        private final JITCounts counts;
        private final String digestString;
        private CallConfiguration jitCallConfig;
        private byte[] bytecode;
        private String name;
    }
    public Block newCompiledClosure(ThreadContext contextIterNode iterNodeIRubyObject self) {
        Binding binding = context.currentBinding(self);
        NodeType argsNodeId = getArgumentTypeWackyHack(iterNode);
        boolean hasMultipleArgsHead = false;
        if (iterNode.getVarNode() instanceof MultipleAsgnNode) {
            hasMultipleArgsHead = ((MultipleAsgnNodeiterNode.getVarNode()).getHeadNode() != null;
        }
        BlockBody body = new CompiledBlock(Arity.procArityOf(iterNode.getVarNode()), iterNode.getScope(), compileBlock(contextnew StandardASMCompiler("blahfooblah" + System.currentTimeMillis(), "blahfooblah"), iterNode), hasMultipleArgsHead, BlockBody.asArgumentType(argsNodeId));
        return new Block(bodybinding);
    }
    public BlockBody newCompiledBlockBody(ThreadContext contextIterNode iterNodeArity arityint argumentType) {
        NodeType argsNodeId = getArgumentTypeWackyHack(iterNode);
        boolean hasMultipleArgsHead = false;
        if (iterNode.getVarNode() instanceof MultipleAsgnNode) {
            hasMultipleArgsHead = ((MultipleAsgnNodeiterNode.getVarNode()).getHeadNode() != null;
        }
        return new CompiledBlock(Arity.procArityOf(iterNode.getVarNode()), iterNode.getScope(), compileBlock(contextnew StandardASMCompiler("blahfooblah" + System.currentTimeMillis(), "blahfooblah"), iterNode), hasMultipleArgsHead, BlockBody.asArgumentType(argsNodeId));
    }
    // ENEBO: Some of this logic should be put back into the Nodes themselves, but the more
    // esoteric features of 1.9 make this difficult to know how to do this yet.
    public BlockBody newCompiledBlockBody19(ThreadContext contextIterNode iterNode) {
        final ArgsNode argsNode = (ArgsNode)iterNode.getVarNode();
        boolean hasMultipleArgsHead = false;
        if (iterNode.getVarNode() instanceof MultipleAsgnNode) {
            hasMultipleArgsHead = ((MultipleAsgnNodeiterNode.getVarNode()).getHeadNode() != null;
        }
        NodeType argsNodeId = BlockBody.getArgumentTypeWackyHack(iterNode);
        
        return new CompiledBlock19(((ArgsNode)iterNode.getVarNode()).getArity(), iterNode.getScope(), compileBlock19(contextnew StandardASMCompiler("blahfooblah" + System.currentTimeMillis(), "blahfooblah"), iterNode), hasMultipleArgsHead, BlockBody.asArgumentType(argsNodeId), Helpers.encodeParameterList(argsNode).split(";"));
    }
    
    public CompiledBlockCallback compileBlock(ThreadContext contextStandardASMCompiler asmCompilerfinal IterNode iterNode) {
        final ASTCompiler astCompiler = new ASTCompiler();
        final StaticScope scope = iterNode.getScope();
        
        asmCompiler.startScript(scope);
        
        // create the closure class and instantiate it
        final CompilerCallback closureBody = new CompilerCallback() {
                    public void call(BodyCompiler context) {
                        if (iterNode.getBodyNode() != null) {
                            astCompiler.compile(iterNode.getBodyNode(), contexttrue);
                        } else {
                            context.loadNil();
                        }
                    }
                };
        // create the closure class and instantiate it
        final CompilerCallback closureArgs = new CompilerCallback() {
            public void call(BodyCompiler context) {
                if (iterNode.getVarNode() != null) {
                    astCompiler.compileAssignment(iterNode.getVarNode(), context);
                } else {
                    context.consumeCurrentValue();
                }
                if (iterNode.getBlockVarNode() != null) {
                    astCompiler.compileAssignment(iterNode.getBlockVarNode(), context);
                } else {
                    context.consumeCurrentValue();
                }
            }
        };
        ASTInspector inspector = new ASTInspector();
        inspector.inspect(iterNode.getBodyNode());
        inspector.inspect(iterNode.getVarNode());
        
        int scopeIndex = asmCompiler.getCacheCompiler().reserveStaticScope();
        ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(asmCompiler"__file__"asmCompiler.getClassname(), inspectorscopescopeIndex);
        
        closureCompiler.beginMethod(closureArgsscope);
        
        closureBody.call(closureCompiler);
        
        closureCompiler.endBody();
        
        // __file__ method with [] args; no-op
        SkinnyMethodAdapter method = new SkinnyMethodAdapter(asmCompiler.getClassVisitor(), ACC_PUBLIC, "__file__"getMethodSignature(4), nullnull);
        method.start();
        method.aload();
        method.areturn();
        method.end();
        
        // __file__ method to call static version
        method = new SkinnyMethodAdapter(asmCompiler.getClassVisitor(), ACC_PUBLIC, "__file__"getMethodSignature(1), nullnull);
        method.start();
        // invoke static __file__
        method.aload();
        method.aload();
        method.aload();
        method.aload();
        method.aload( + 1); // block
        method.invokestatic(asmCompiler.getClassname(), "__file__"getStaticMethodSignature(asmCompiler.getClassname(), 1));
        method.areturn();
        method.end();
        
        asmCompiler.endScript(falsefalse);
        
        byte[] bytes = asmCompiler.getClassByteArray();
        Class blockClass = new JRubyClassLoader(context.runtime.getJRubyClassLoader()).defineClass(asmCompiler.getClassname(), bytes);
        try {
            final AbstractScript script = (AbstractScript)blockClass.newInstance();
            script.setRootScope(scope);
            
            return new CompiledBlockCallback() {
                @Override
                public IRubyObject call(ThreadContext contextIRubyObject selfIRubyObject argsBlock block) {
                    return script.__file__(contextselfargsblock);
                }
                @Override
                public String getFile() {
                    return "blah";
                }
                @Override
                public int getLine() {
                    return -1;
                }
            };
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public CompiledBlockCallback19 compileBlock19(ThreadContext contextStandardASMCompiler asmCompilerfinal IterNode iterNode) {
        final ASTCompiler19 astCompiler = new ASTCompiler19();
        final StaticScope scope = iterNode.getScope();
        
        asmCompiler.startScript(scope);
        
        final ArgsNode argsNode = (ArgsNode)iterNode.getVarNode();
        // create the closure class and instantiate it
        final CompilerCallback closureBody = new CompilerCallback() {
            public void call(BodyCompiler context) {
                if (iterNode.getBodyNode() != null) {
                    astCompiler.compile(iterNode.getBodyNode(), contexttrue);
                } else {
                    context.loadNil();
                }
            }
        };
        // create the closure class and instantiate it
        final CompilerCallback closureArgs = new CompilerCallback() {
            public void call(BodyCompiler context) {
                // FIXME: This is temporary since the variable compilers assume we want
                // args already on stack for assignment. We just pop and continue with
                // 1.9 args logic.
                context.consumeCurrentValue(); // args value
                context.consumeCurrentValue(); // passed block
                if (iterNode.getVarNode() != null) {
                    if (iterNode instanceof LambdaNode) {
                        final int required = argsNode.getRequiredArgsCount();
                        final int opt = argsNode.getOptionalArgsCount();
                        final int rest = argsNode.getRestArg();
                        context.getVariableCompiler().checkMethodArity(requiredoptrest);
                        astCompiler.compileMethodArgs(argsNodecontexttrue);
                    } else {
                        astCompiler.compileMethodArgs(argsNodecontexttrue);
                    }
                }
            }
        };
        ASTInspector inspector = new ASTInspector();
        inspector.inspect(iterNode.getBodyNode());
        inspector.inspect(iterNode.getVarNode());
        
        NodeType argsNodeId = BlockBody.getArgumentTypeWackyHack(iterNode);
        
        int scopeIndex = asmCompiler.getCacheCompiler().reserveStaticScope();
        ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler19(asmCompiler"__file__"asmCompiler.getClassname(), inspectorscopescopeIndex);
        
        closureCompiler.beginMethod(argsNodeId == null ? null : closureArgsscope);
        
        closureBody.call(closureCompiler);
        
        closureCompiler.endBody();
        
        // __file__ method to call static version
        SkinnyMethodAdapter method = new SkinnyMethodAdapter(asmCompiler.getClassVisitor(), ACC_PUBLIC, "__file__"getMethodSignature(4), nullnull);
        method.start();
        // invoke static __file__
        method.aload();
        method.aload();
        method.aload();
        method.aload();
        method.aload( + 1); // block
        method.invokestatic(asmCompiler.getClassname(), "__file__"asmCompiler.getStaticMethodSignature(asmCompiler.getClassname(), 4));
        method.areturn();
        method.end();
        
        asmCompiler.endScript(falsefalse);
        
        byte[] bytes = asmCompiler.getClassByteArray();
        Class blockClass = new JRubyClassLoader(context.runtime.getJRubyClassLoader()).defineClass(asmCompiler.getClassname(), bytes);
        try {
            final AbstractScript script = (AbstractScript)blockClass.newInstance();
            script.setRootScope(scope);
            
            return new CompiledBlockCallback19() {
                @Override
                public IRubyObject call(ThreadContext contextIRubyObject selfIRubyObject[] argsBlock block) {
                    return script.__file__(contextselfargsblock);
                }
                @Override
                public String getFile() {
                    return iterNode.getPosition().getFile();
                }
                @Override
                public int getLine() {
                    return iterNode.getPosition().getLine();
                }
            };
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    static void log(DefaultMethod methodString nameString messageString... reason) {
        String className = method.getImplementationClass().getBaseName();
        
        if (className == nullclassName = "<anon class>";
        StringBuilder builder = new StringBuilder(message + ":" + className + "." + name + " at " + method.getPosition());
        
        if (reason.length > 0) {
            builder.append(" because of: \"");
            for (int i = 0; i < reason.lengthi++) {
                builder.append(reason[i]);
            }
            builder.append('"');
        }
        
        .info(builder.toString());
    }
    
    private static String calculateFilename(ArgsNode argsNodeNode bodyNode) {
        if (bodyNode != nullreturn bodyNode.getPosition().getFile();
        if (argsNode != nullreturn argsNode.getPosition().getFile();
        
        return "__eval__";
    }
New to GrepCode? Check out our FAQ X