From 9ed503430bcc13d58266351ece0d024ea320d11c Mon Sep 17 00:00:00 2001 From: Andreas Woess Date: Thu, 19 Sep 2024 17:45:27 +0200 Subject: [PATCH] [GR-58286] Fix const binding increment, decrement, and compound assignment. --- .../truffle/js/parser/GraalJSTranslator.java | 13 ++++-- .../js/const_reassign_ident_global.js | 40 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 graal-js/src/com.oracle.truffle.js.test/js/const_reassign_ident_global.js diff --git a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java index 4935a04a28f..c89584872aa 100644 --- a/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java +++ b/graal-js/src/com.oracle.truffle.js.parser/src/com/oracle/truffle/js/parser/GraalJSTranslator.java @@ -2885,15 +2885,19 @@ private JavaScriptNode transformAssignmentIdent(IdentNode identNode, JavaScriptN // if scopeVar is const, the assignment will never succeed and is only there to perform // the temporal dead zone check and throw a ReferenceError instead of a TypeError - if (!initializationAssignment && scopeVar.isConst()) { - rhs = checkMutableBinding(rhs, scopeVar.getName()); - } + boolean constAssignment = !initializationAssignment && scopeVar.isConst(); if (binaryOp == null) { + if (constAssignment) { + rhs = checkMutableBinding(rhs, scopeVar.getName()); + } return scopeVar.createWriteNode(rhs); } else { if (isLogicalOp(binaryOp)) { assert !convertLHSToNumeric && !returnOldValue; + if (constAssignment) { + rhs = checkMutableBinding(rhs, scopeVar.getName()); + } JavaScriptNode readNode = tagExpression(scopeVar.createReadNode(), identNode); JavaScriptNode writeNode = scopeVar.createWriteNode(rhs); return factory.createBinary(context, binaryOp, readNode, writeNode); @@ -2913,6 +2917,9 @@ private JavaScriptNode transformAssignmentIdent(IdentNode identNode, JavaScriptN readNode = prevValueTemp.createWriteNode(readNode); } JavaScriptNode binOpNode = tagExpression(factory.createBinary(context, binaryOp, readNode, rhs), identNode); + if (constAssignment) { + binOpNode = checkMutableBinding(binOpNode, scopeVar.getName()); + } JavaScriptNode writeNode = pair.getSecond().apply(binOpNode); if (returnOldValue) { return factory.createDual(context, writeNode, prevValueTemp.createReadNode()); diff --git a/graal-js/src/com.oracle.truffle.js.test/js/const_reassign_ident_global.js b/graal-js/src/com.oracle.truffle.js.test/js/const_reassign_ident_global.js new file mode 100644 index 00000000000..0725d761079 --- /dev/null +++ b/graal-js/src/com.oracle.truffle.js.test/js/const_reassign_ident_global.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at http://oss.oracle.com/licenses/upl. + */ + +/** + * Tests correct immutable binding reassignment handling for `const` variables in the global scope + * via increment (++), decrement (--), and compound assignment operators. + */ + +const i = 4; try { i++; fail(); } catch (e) { if (!(e instanceof TypeError)) throw e; } +const j = 4; try { ++j; fail(); } catch (e) { if (!(e instanceof TypeError)) throw e; } +const k = 4; try { k--; fail(); } catch (e) { if (!(e instanceof TypeError)) throw e; } +const l = 4; try { --l; fail(); } catch (e) { if (!(e instanceof TypeError)) throw e; } + +// If binding is immutable, compound assignment should fail only after the binary operator (including both its operands) has been executed. +let side_effects = 0; +const m = {toString() { side_effects += 1; return "a"; }}; +try { + m += {toString() { side_effects += 2; return "b"; }}; + fail(); +} catch (e) { + if (!(e instanceof TypeError)) throw e; +} +if (side_effects !== 3) throw new Error(`${side_effects}`); + +// TypeError due to mixing BigInt and Number is thrown first. +const n = 1n; +try { + n += 1; + fail(); +} catch (e) { + if (!(e instanceof TypeError && e.message.includes("BigInt"))) throw e; +} + +function fail() { + throw new Error("Expected a TypeError, but no error was thrown."); +}