# HG changeset patch # User sundar # Date 1381398536 -7200 # Node ID 7cc5ff16380fc2b546e2e0f97b3037adb3ca2b22 # Parent 03a68e7ca1d5ef261c0c3055733e80fc75f9b44c 8026167: Class cache/reuse of 'eval' scripts results in ClassCastException in some cases. Reviewed-by: lagergren, jlaskey diff -r 03a68e7ca1d5 -r 7cc5ff16380f src/jdk/nashorn/internal/codegen/CompilationPhase.java --- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java Wed Oct 09 17:53:22 2013 +0200 +++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java Thu Oct 10 11:48:56 2013 +0200 @@ -162,7 +162,7 @@ LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) { @Override FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - return (FunctionNode)fn.accept(new Lower()); + return (FunctionNode)fn.accept(new Lower(compiler)); } @Override diff -r 03a68e7ca1d5 -r 7cc5ff16380f src/jdk/nashorn/internal/codegen/Lower.java --- a/src/jdk/nashorn/internal/codegen/Lower.java Wed Oct 09 17:53:22 2013 +0200 +++ b/src/jdk/nashorn/internal/codegen/Lower.java Thu Oct 10 11:48:56 2013 +0200 @@ -86,10 +86,13 @@ private static final DebugLogger LOG = new DebugLogger("lower"); + // needed only to get unique eval id from code installer + private final Compiler compiler; + /** * Constructor. */ - Lower() { + Lower(final Compiler compiler) { super(new BlockLexicalContext() { @Override @@ -132,6 +135,7 @@ return block.setIsTerminal(this, false); } }); + this.compiler = compiler; } @Override @@ -529,11 +533,15 @@ */ private String evalLocation(final IdentNode node) { final Source source = lc.getCurrentFunction().getSource(); + final int pos = node.position(); return new StringBuilder(). append(source.getName()). append('#'). - append(source.getLine(node.position())). - append(""). + append(source.getLine(pos)). + append(':'). + append(source.getColumn(pos)). + append("@"). + append(compiler.getCodeInstaller().getUniqueEvalId()). toString(); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f src/jdk/nashorn/internal/runtime/CodeInstaller.java --- a/src/jdk/nashorn/internal/runtime/CodeInstaller.java Wed Oct 09 17:53:22 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/CodeInstaller.java Thu Oct 10 11:48:56 2013 +0200 @@ -68,4 +68,10 @@ * @return unique script id */ public long getUniqueScriptId(); + + /** + * Get next unique eval id + * @return unique eval id + */ + public long getUniqueEvalId(); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f src/jdk/nashorn/internal/runtime/Context.java --- a/src/jdk/nashorn/internal/runtime/Context.java Wed Oct 09 17:53:22 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/Context.java Thu Oct 10 11:48:56 2013 +0200 @@ -139,6 +139,11 @@ public long getUniqueScriptId() { return context.getUniqueScriptId(); } + + @Override + public long getUniqueEvalId() { + return context.getUniqueEvalId(); + } } /** Is Context global debug mode enabled ? */ @@ -238,6 +243,9 @@ /** Unique id for script. Used only when --loader-per-compile=false */ private final AtomicLong uniqueScriptId; + /** Unique id for 'eval' */ + private final AtomicLong uniqueEvalId; + private static final ClassLoader myLoader = Context.class.getClassLoader(); private static final StructureLoader sharedLoader; @@ -320,6 +328,7 @@ this.uniqueScriptId = new AtomicLong(); } this.errors = errors; + this.uniqueEvalId = new AtomicLong(); // if user passed -classpath option, make a class loader with that and set it as // thread context class loader so that script can access classes from that path. @@ -954,6 +963,10 @@ }, CREATE_LOADER_ACC_CTXT); } + private long getUniqueEvalId() { + return uniqueEvalId.getAndIncrement(); + } + private long getUniqueScriptId() { return uniqueScriptId.getAndIncrement(); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/assert.js --- a/test/script/assert.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/assert.js Thu Oct 10 11:48:56 2013 +0200 @@ -61,3 +61,22 @@ } } }); + +Object.defineProperty(this, "printError", { + configuable: true, + enumerable: false, + writable: true, + value: function (e) { + var msg = e.message; + var str = e.name + ':'; + if (e.lineNumber > 0) { + str += e.lineNumber + ':'; + } + if (e.columnNumber > 0) { + str += e.columnNumber + ':'; + } + str += msg.substring(msg.indexOf(' ') + 1); + print(str); + } +}); + diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019508.js --- a/test/script/basic/JDK-8019508.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019508.js Thu Oct 10 11:48:56 2013 +0200 @@ -36,7 +36,7 @@ if (! (e instanceof SyntaxError)) { fail("expected SyntaxError, got " + e); } - print(e.message.replace(/\\/g, '/')); + printError(e); } } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019508.js.EXPECTED --- a/test/script/basic/JDK-8019508.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019508.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,12 +1,12 @@ -test/script/basic/JDK-8019508.js#33:1:2 Expected property id but found , +SyntaxError:33:Expected property id but found , ({,}) ^ -test/script/basic/JDK-8019508.js#33:1:2 Expected property id but found , +SyntaxError:33:Expected property id but found , ({, a:2 }) ^ -test/script/basic/JDK-8019508.js#33:1:6 Expected property id but found , +SyntaxError:33:Expected property id but found , ({a:3,,}) ^ -test/script/basic/JDK-8019508.js#33:1:6 Expected comma but found ident +SyntaxError:33:Expected comma but found ident ({a:3 b:2} ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019553.js --- a/test/script/basic/JDK-8019553.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019553.js Thu Oct 10 11:48:56 2013 +0200 @@ -33,7 +33,7 @@ eval(str); fail("SyntaxError expected for: " + str); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019553.js.EXPECTED --- a/test/script/basic/JDK-8019553.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019553.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,12 +1,12 @@ -SyntaxError: test/script/basic/JDK-8019553.js#33:1:3 Expected l-value but found + +SyntaxError:33:Expected l-value but found + ++ +3 ^ -SyntaxError: test/script/basic/JDK-8019553.js#33:1:3 Expected l-value but found - +SyntaxError:33:Expected l-value but found - ++ -7 ^ -SyntaxError: test/script/basic/JDK-8019553.js#33:1:3 Expected l-value but found + +SyntaxError:33:Expected l-value but found + -- +2 ^ -SyntaxError: test/script/basic/JDK-8019553.js#33:1:3 Expected l-value but found - +SyntaxError:33:Expected l-value but found - -- -8 ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019791.js --- a/test/script/basic/JDK-8019791.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019791.js Thu Oct 10 11:48:56 2013 +0200 @@ -33,7 +33,7 @@ eval('"" ~ ""'); print("FAILED: SyntaxError expected for: \"\" ~ \"\""); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } // Used to crash instead of SyntaxError @@ -41,7 +41,7 @@ eval("function() { if (1~0) return 0; return 1 }"); print("FAILED: SyntaxError expected for: if (1~0) "); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } // The following are valid, but used to crash diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019791.js.EXPECTED --- a/test/script/basic/JDK-8019791.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019791.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,6 +1,6 @@ -SyntaxError: test/script/basic/JDK-8019791.js#33:1:3 Expected ; but found ~ +SyntaxError:33:Expected ; but found ~ "" ~ "" ^ -SyntaxError: test/script/basic/JDK-8019791.js#41:1:18 Expected ) but found ~ +SyntaxError:41:Expected ) but found ~ function() { if (1~0) return 0; return 1 } ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019805.js --- a/test/script/basic/JDK-8019805.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019805.js Thu Oct 10 11:48:56 2013 +0200 @@ -32,5 +32,5 @@ eval("for each(var v=0;false;);"); print("FAILED: for each(var v=0; false;); should have thrown error"); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8019805.js.EXPECTED --- a/test/script/basic/JDK-8019805.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/JDK-8019805.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,3 +1,3 @@ -SyntaxError: test/script/basic/JDK-8019805.js#32:1:16 for each can only be used with for..in +SyntaxError:32:for each can only be used with for..in for each(var v=0;false;); ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/JDK-8026167.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/script/basic/JDK-8026167.js Thu Oct 10 11:48:56 2013 +0200 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * JDK-8026167: Class cache/reuse of 'eval' scripts results in ClassCastException in some cases. + * + * @test + * @run + */ + +var m = new javax.script.ScriptEngineManager(); +var e = m.getEngineByName('js'); + +// leave the whitespace - need both eval("e") at same column for this test! + +e.eval('function f(e) { eval("e") } f()'); +e.eval('function f() { var e = 33; eval("e") } f()'); + +function f() { + Function.call.call(function x() { eval("x") }); eval("x") +} + +try { + f(); + fail("Should have thrown ReferenceError"); +} catch (e) { + if (! (e instanceof ReferenceError)) { + fail("ReferenceError expected but got " + e); + } +} diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-100.js --- a/test/script/basic/NASHORN-100.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-100.js Thu Oct 10 11:48:56 2013 +0200 @@ -35,5 +35,5 @@ if (! (e instanceof SyntaxError)) { fail("#2 expected SyntaxError got " + e); } - print(e.toString().replace(/\\/g, '/')); + printError(e); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-100.js.EXPECTED --- a/test/script/basic/NASHORN-100.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-100.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,3 +1,3 @@ -SyntaxError: test/script/basic/NASHORN-100.js#32:1:0 Invalid return statement +SyntaxError:32:Invalid return statement return; ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-293.js --- a/test/script/basic/NASHORN-293.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-293.js Thu Oct 10 11:48:56 2013 +0200 @@ -40,15 +40,13 @@ try { eval(src); } catch (e) { - var location = e.fileName ? e.fileName.slice(-9) : "unknown source"; - print(e.name, "@", location); + printError(e); } } for (var i = 0; i < 3; i++) { try { eval(src); } catch (e) { - var location = e.fileName ? e.fileName.slice(-9) : "unknown source"; - print(e.name, "@", location); + printError(e); } } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-293.js.EXPECTED --- a/test/script/basic/NASHORN-293.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-293.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,9 +1,9 @@ hello hello hello -TypeError @ #41 -TypeError @ #41 -TypeError @ #41 -TypeError @ #49 -TypeError @ #49 -TypeError @ #49 +TypeError:1:read property "foo" from undefined +TypeError:1:read property "foo" from undefined +TypeError:1:read property "foo" from undefined +TypeError:1:read property "foo" from undefined +TypeError:1:read property "foo" from undefined +TypeError:1:read property "foo" from undefined diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-40.js --- a/test/script/basic/NASHORN-40.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-40.js Thu Oct 10 11:48:56 2013 +0200 @@ -31,11 +31,11 @@ try { eval("print(.foo)"); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } try { eval(".bar = 3423;"); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-40.js.EXPECTED --- a/test/script/basic/NASHORN-40.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-40.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,6 +1,6 @@ -SyntaxError: test/script/basic/NASHORN-40.js#32:1:6 Expected an operand but found . +SyntaxError:32:Expected an operand but found . print(.foo) ^ -SyntaxError: test/script/basic/NASHORN-40.js#38:1:0 Expected an operand but found . +SyntaxError:38:Expected an operand but found . .bar = 3423; ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-51.js --- a/test/script/basic/NASHORN-51.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-51.js Thu Oct 10 11:48:56 2013 +0200 @@ -35,28 +35,28 @@ eval(literals[i] + "++"); print("ERROR!! post increment : " + literals[i]); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } try { eval(literals[i] + "--"); print("ERROR!! post decrement : " + literals[i]); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } try { eval("++" + literals[i]); print("ERROR!! pre increment : " + literals[i]); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } try { eval("--" + literals[i]); print("ERROR!! pre decrement : " + literals[i]); } catch (e) { - print(e.toString().replace(/\\/g, '/')); + printError(e); } } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-51.js.EXPECTED --- a/test/script/basic/NASHORN-51.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-51.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,72 +1,72 @@ -ReferenceError: test/script/basic/NASHORN-51.js#35:1:0 Invalid left hand side for assignment +ReferenceError:35:Invalid left hand side for assignment 1++ ^ -ReferenceError: test/script/basic/NASHORN-51.js#42:1:0 Invalid left hand side for assignment +ReferenceError:42:Invalid left hand side for assignment 1-- ^ -ReferenceError: test/script/basic/NASHORN-51.js#49:1:2 Invalid left hand side for assignment +ReferenceError:49:Invalid left hand side for assignment ++1 ^ -ReferenceError: test/script/basic/NASHORN-51.js#56:1:2 Invalid left hand side for assignment +ReferenceError:56:Invalid left hand side for assignment --1 ^ -ReferenceError: test/script/basic/NASHORN-51.js#35:1:0 Invalid left hand side for assignment +ReferenceError:35:Invalid left hand side for assignment 0++ ^ -ReferenceError: test/script/basic/NASHORN-51.js#42:1:0 Invalid left hand side for assignment +ReferenceError:42:Invalid left hand side for assignment 0-- ^ -ReferenceError: test/script/basic/NASHORN-51.js#49:1:2 Invalid left hand side for assignment +ReferenceError:49:Invalid left hand side for assignment ++0 ^ -ReferenceError: test/script/basic/NASHORN-51.js#56:1:2 Invalid left hand side for assignment +ReferenceError:56:Invalid left hand side for assignment --0 ^ -ReferenceError: test/script/basic/NASHORN-51.js#35:1:0 Invalid left hand side for assignment +ReferenceError:35:Invalid left hand side for assignment 3.14++ ^ -ReferenceError: test/script/basic/NASHORN-51.js#42:1:0 Invalid left hand side for assignment +ReferenceError:42:Invalid left hand side for assignment 3.14-- ^ -ReferenceError: test/script/basic/NASHORN-51.js#49:1:2 Invalid left hand side for assignment +ReferenceError:49:Invalid left hand side for assignment ++3.14 ^ -ReferenceError: test/script/basic/NASHORN-51.js#56:1:2 Invalid left hand side for assignment +ReferenceError:56:Invalid left hand side for assignment --3.14 ^ -ReferenceError: test/script/basic/NASHORN-51.js#35:1:0 Invalid left hand side for assignment +ReferenceError:35:Invalid left hand side for assignment true++ ^ -ReferenceError: test/script/basic/NASHORN-51.js#42:1:0 Invalid left hand side for assignment +ReferenceError:42:Invalid left hand side for assignment true-- ^ -ReferenceError: test/script/basic/NASHORN-51.js#49:1:2 Invalid left hand side for assignment +ReferenceError:49:Invalid left hand side for assignment ++true ^ -ReferenceError: test/script/basic/NASHORN-51.js#56:1:2 Invalid left hand side for assignment +ReferenceError:56:Invalid left hand side for assignment --true ^ -ReferenceError: test/script/basic/NASHORN-51.js#35:1:0 Invalid left hand side for assignment +ReferenceError:35:Invalid left hand side for assignment false++ ^ -ReferenceError: test/script/basic/NASHORN-51.js#42:1:0 Invalid left hand side for assignment +ReferenceError:42:Invalid left hand side for assignment false-- ^ -ReferenceError: test/script/basic/NASHORN-51.js#49:1:2 Invalid left hand side for assignment +ReferenceError:49:Invalid left hand side for assignment ++false ^ -ReferenceError: test/script/basic/NASHORN-51.js#56:1:2 Invalid left hand side for assignment +ReferenceError:56:Invalid left hand side for assignment --false ^ -ReferenceError: test/script/basic/NASHORN-51.js#35:1:0 Invalid left hand side for assignment +ReferenceError:35:Invalid left hand side for assignment null++ ^ -ReferenceError: test/script/basic/NASHORN-51.js#42:1:0 Invalid left hand side for assignment +ReferenceError:42:Invalid left hand side for assignment null-- ^ -ReferenceError: test/script/basic/NASHORN-51.js#49:1:2 Invalid left hand side for assignment +ReferenceError:49:Invalid left hand side for assignment ++null ^ -ReferenceError: test/script/basic/NASHORN-51.js#56:1:2 Invalid left hand side for assignment +ReferenceError:56:Invalid left hand side for assignment --null ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-98.js --- a/test/script/basic/NASHORN-98.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-98.js Thu Oct 10 11:48:56 2013 +0200 @@ -34,7 +34,7 @@ if (! (e instanceof SyntaxError)) { fail("syntax error expected here got " + e); } - print(e.toString().replace(/\\/g, '/')); + printError(e); } try { @@ -43,5 +43,5 @@ if (! (e instanceof SyntaxError)) { fail("syntax error expected here got " + e); } - print(e.toString().replace(/\\/g, '/')); + printError(e); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/NASHORN-98.js.EXPECTED --- a/test/script/basic/NASHORN-98.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/NASHORN-98.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -1,6 +1,6 @@ -SyntaxError: test/script/basic/NASHORN-98.js#32:1:13 Expected comma but found decimal +SyntaxError:32:Expected comma but found decimal var x = [ 23 34 ] ^ -SyntaxError: test/script/basic/NASHORN-98.js#41:1:18 Expected comma but found ident +SyntaxError:41:Expected comma but found ident var x = { foo: 33 bar: 'hello' } ^ diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/eval.js --- a/test/script/basic/eval.js Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/eval.js Thu Oct 10 11:48:56 2013 +0200 @@ -69,5 +69,5 @@ eval("print('hello)"); } catch (e) { print("is syntax error? " + (e instanceof SyntaxError)); - print(e.toString().replace(/\\/g, '/')); + printError(e); } diff -r 03a68e7ca1d5 -r 7cc5ff16380f test/script/basic/eval.js.EXPECTED --- a/test/script/basic/eval.js.EXPECTED Wed Oct 09 17:53:22 2013 +0200 +++ b/test/script/basic/eval.js.EXPECTED Thu Oct 10 11:48:56 2013 +0200 @@ -10,6 +10,6 @@ 100 3300 is syntax error? true -SyntaxError: test/script/basic/eval.js#69:1:13 Missing close quote +SyntaxError:69:Missing close quote print('hello) ^