diff --git a/src/ast/nodes/CallExpression.ts b/src/ast/nodes/CallExpression.ts index 68daeb86768..d89e4b9ecdc 100644 --- a/src/ast/nodes/CallExpression.ts +++ b/src/ast/nodes/CallExpression.ts @@ -17,6 +17,7 @@ import { } from '../utils/PathTracker'; import { LiteralValueOrUnknown, UNKNOWN_EXPRESSION, UnknownValue } from '../values'; import Identifier from './Identifier'; +import MemberExpression from './MemberExpression'; import * as NodeType from './NodeType'; import { ExpressionEntity } from './shared/Expression'; import { ExpressionNode, INCLUDE_PARAMETERS, IncludeChildren, NodeBase } from './shared/Node'; @@ -66,6 +67,9 @@ export default class CallExpression extends NodeBase implements DeoptimizableEnt this ); } + if (this.callee instanceof MemberExpression && !this.callee.variable) { + this.callee.object.deoptimizePath(UNKNOWN_PATH); + } for (const argument of this.arguments) { // This will make sure all properties of parameters behave as "unknown" argument.deoptimizePath(UNKNOWN_PATH); diff --git a/test/form/samples/arrow-function-return-values/_expected.js b/test/form/samples/arrow-function-return-values/_expected.js index a3fd2df3d87..1bedb0a7bbe 100644 --- a/test/form/samples/arrow-function-return-values/_expected.js +++ b/test/form/samples/arrow-function-return-values/_expected.js @@ -6,5 +6,9 @@ retained1()(); (() => { return () => console.log( 'effect' ); })()(); + +(() => ({ foo: () => {} }))().foo(); (() => ({ foo: () => console.log( 'effect' ) }))().foo(); + +(() => ({ foo: () => ({ bar: () => ({ baz: () => {} }) }) }))().foo().bar().baz(); (() => ({ foo: () => ({ bar: () => console.log( 'effect' ) }) }))().foo().bar(); diff --git a/test/form/samples/builtin-prototypes/object-expression/_expected.js b/test/form/samples/builtin-prototypes/object-expression/_expected.js index 8b137891791..4a0697eea28 100644 --- a/test/form/samples/builtin-prototypes/object-expression/_expected.js +++ b/test/form/samples/builtin-prototypes/object-expression/_expected.js @@ -1 +1,11 @@ +const object = {}; +const propertyIsEnumerable1 = object.propertyIsEnumerable( 'toString' ); +const propertyIsEnumerable2 = {}.propertyIsEnumerable( 'toString' ); +const propertyIsEnumerable3 = {}.propertyIsEnumerable( 'toString' ).valueOf(); +const _hasOwnProperty = {}.hasOwnProperty( 'toString' ).valueOf(); +const _isPrototypeOf = {}.isPrototypeOf( {} ).valueOf(); +const _propertyIsEnumerable = {}.propertyIsEnumerable( 'toString' ).valueOf(); +const _toLocaleString = {}.toLocaleString().trim(); +const _toString = {}.toString().trim(); +const _valueOf = {}.valueOf(); diff --git a/test/form/samples/conditional-expression-paths/_expected.js b/test/form/samples/conditional-expression-paths/_expected.js index 882a3d152d2..f40be0e12af 100644 --- a/test/form/samples/conditional-expression-paths/_expected.js +++ b/test/form/samples/conditional-expression-paths/_expected.js @@ -1,11 +1,22 @@ var unknownValue = globalThis.unknown(); var foo = { x: () => {}, y: {} }; +var bar = { x: () => {}, y: {} }; var baz = { x: () => console.log('effect') }; +// unknown branch without side-effects +var a1 = (unknownValue ? foo : bar).y.z; +var b1 = (unknownValue ? foo : bar).x(); + // unknown branch with side-effect var a2 = (unknownValue ? foo : baz).y.z; var b2 = (unknownValue ? foo : baz).x(); +// known branch without side-effects +var a3 = ( foo ).y.z; +var b3 = ( foo).y.z; +var c3 = ( foo ).x(); +var d3 = ( foo).x(); + // known branch with side-effect var a4 = ( baz ).y.z; var b4 = ( baz).y.z; diff --git a/test/form/samples/function-body-return-values/_expected.js b/test/form/samples/function-body-return-values/_expected.js index 0064a3f4c4a..cd4f77d4ce6 100644 --- a/test/form/samples/function-body-return-values/_expected.js +++ b/test/form/samples/function-body-return-values/_expected.js @@ -1,3 +1,9 @@ +function removed3 () { + return { x: () => {} }; +} + +removed3().x(); + function retained1 () { return () => console.log( 'effect' ); } diff --git a/test/form/samples/getter-return-values/_expected/cjs.js b/test/form/samples/getter-return-values/_expected.js similarity index 80% rename from test/form/samples/getter-return-values/_expected/cjs.js rename to test/form/samples/getter-return-values/_expected.js index 70a48fa585a..7682aece034 100644 --- a/test/form/samples/getter-return-values/_expected/cjs.js +++ b/test/form/samples/getter-return-values/_expected.js @@ -1,5 +1,3 @@ -'use strict'; - ({ get foo () { console.log( 'effect' ); @@ -11,6 +9,12 @@ return {}; } }).foo.bar.baz; + +({ + get foo () { + return () => {}; + } +}).foo(); ({ get foo () { console.log( 'effect' ); @@ -22,6 +26,12 @@ return () => console.log( 'effect' ); } }).foo(); + +({ + get foo () { + return () => () => {}; + } +}).foo()(); ({ get foo () { console.log( 'effect' ); diff --git a/test/form/samples/getter-return-values/_expected/amd.js b/test/form/samples/getter-return-values/_expected/amd.js deleted file mode 100644 index da5dcb9b95d..00000000000 --- a/test/form/samples/getter-return-values/_expected/amd.js +++ /dev/null @@ -1,37 +0,0 @@ -define(function () { 'use strict'; - - ({ - get foo () { - console.log( 'effect' ); - return {}; - } - }).foo.bar; - ({ - get foo () { - return {}; - } - }).foo.bar.baz; - ({ - get foo () { - console.log( 'effect' ); - return () => {}; - } - }).foo(); - ({ - get foo () { - return () => console.log( 'effect' ); - } - }).foo(); - ({ - get foo () { - console.log( 'effect' ); - return () => () => {}; - } - }).foo()(); - ({ - get foo () { - return () => () => console.log( 'effect' ); - } - }).foo()(); - -}); diff --git a/test/form/samples/getter-return-values/_expected/es.js b/test/form/samples/getter-return-values/_expected/es.js deleted file mode 100644 index 61ea5587251..00000000000 --- a/test/form/samples/getter-return-values/_expected/es.js +++ /dev/null @@ -1,33 +0,0 @@ -({ - get foo () { - console.log( 'effect' ); - return {}; - } -}).foo.bar; -({ - get foo () { - return {}; - } -}).foo.bar.baz; -({ - get foo () { - console.log( 'effect' ); - return () => {}; - } -}).foo(); -({ - get foo () { - return () => console.log( 'effect' ); - } -}).foo(); -({ - get foo () { - console.log( 'effect' ); - return () => () => {}; - } -}).foo()(); -({ - get foo () { - return () => () => console.log( 'effect' ); - } -}).foo()(); diff --git a/test/form/samples/getter-return-values/_expected/iife.js b/test/form/samples/getter-return-values/_expected/iife.js deleted file mode 100644 index bf86e946681..00000000000 --- a/test/form/samples/getter-return-values/_expected/iife.js +++ /dev/null @@ -1,38 +0,0 @@ -(function () { - 'use strict'; - - ({ - get foo () { - console.log( 'effect' ); - return {}; - } - }).foo.bar; - ({ - get foo () { - return {}; - } - }).foo.bar.baz; - ({ - get foo () { - console.log( 'effect' ); - return () => {}; - } - }).foo(); - ({ - get foo () { - return () => console.log( 'effect' ); - } - }).foo(); - ({ - get foo () { - console.log( 'effect' ); - return () => () => {}; - } - }).foo()(); - ({ - get foo () { - return () => () => console.log( 'effect' ); - } - }).foo()(); - -}()); diff --git a/test/form/samples/getter-return-values/_expected/system.js b/test/form/samples/getter-return-values/_expected/system.js deleted file mode 100644 index ee214c148b6..00000000000 --- a/test/form/samples/getter-return-values/_expected/system.js +++ /dev/null @@ -1,42 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - ({ - get foo () { - console.log( 'effect' ); - return {}; - } - }).foo.bar; - ({ - get foo () { - return {}; - } - }).foo.bar.baz; - ({ - get foo () { - console.log( 'effect' ); - return () => {}; - } - }).foo(); - ({ - get foo () { - return () => console.log( 'effect' ); - } - }).foo(); - ({ - get foo () { - console.log( 'effect' ); - return () => () => {}; - } - }).foo()(); - ({ - get foo () { - return () => () => console.log( 'effect' ); - } - }).foo()(); - - } - }; -}); diff --git a/test/form/samples/getter-return-values/_expected/umd.js b/test/form/samples/getter-return-values/_expected/umd.js deleted file mode 100644 index c7cfe78ad26..00000000000 --- a/test/form/samples/getter-return-values/_expected/umd.js +++ /dev/null @@ -1,40 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}((function () { 'use strict'; - - ({ - get foo () { - console.log( 'effect' ); - return {}; - } - }).foo.bar; - ({ - get foo () { - return {}; - } - }).foo.bar.baz; - ({ - get foo () { - console.log( 'effect' ); - return () => {}; - } - }).foo(); - ({ - get foo () { - return () => console.log( 'effect' ); - } - }).foo(); - ({ - get foo () { - console.log( 'effect' ); - return () => () => {}; - } - }).foo()(); - ({ - get foo () { - return () => () => console.log( 'effect' ); - } - }).foo()(); - -}))); diff --git a/test/form/samples/object-expression/method-side-effects/_expected.js b/test/form/samples/object-expression/method-side-effects/_expected.js index b95ee292ad0..d40ea5a9315 100644 --- a/test/form/samples/object-expression/method-side-effects/_expected.js +++ b/test/form/samples/object-expression/method-side-effects/_expected.js @@ -1,3 +1,12 @@ +const x = { + [globalThis.unknown]() { + console.log('effect'); + }, + a() {} +}; + +x.a(); + const y = { a() {}, [globalThis.unknown]() { @@ -12,3 +21,4 @@ const z = { }; z.a(); +z.hasOwnProperty('a'); // removed diff --git a/test/form/samples/object-expression/return-expressions/_expected.js b/test/form/samples/object-expression/return-expressions/_expected.js index 3538f6d9b82..9565727abf5 100644 --- a/test/form/samples/object-expression/return-expressions/_expected.js +++ b/test/form/samples/object-expression/return-expressions/_expected.js @@ -1,3 +1,10 @@ +const x = { + [globalThis.unknown]: () => () => console.log('effect'), + a: () => () => {} +}; + +x.a()(); + const y = { a: () => () => {}, [globalThis.unknown]: () => () => console.log('effect') @@ -12,6 +19,8 @@ const z = { z.a()(); const v = {}; + +v.toString().charCodeAt(0); // removed v.toString().doesNotExist(0); // retained const w = { diff --git a/test/form/samples/object-literal-property-overwrites/_expected.js b/test/form/samples/object-literal-property-overwrites/_expected.js index ecd783e1d52..1f2665f813e 100644 --- a/test/form/samples/object-literal-property-overwrites/_expected.js +++ b/test/form/samples/object-literal-property-overwrites/_expected.js @@ -1,3 +1,23 @@ +const removed1 = { + foo: () => {}, + foo: () => {}, + ['f' + 'oo']: () => {} +}; +removed1.foo(); + +const removed2 = { + foo: () => console.log( 'effect' ), + foo: () => {} +}; +removed2.foo(); + +const removed3 = { + ['fo' + 'o']: function () {this.x = 1;}, + ['f' + 'oo']: () => console.log( 'effect' ), + foo: () => {} +}; +removed3.foo(); + const retained1 = { foo: () => {}, foo: function () {this.x = 1;} diff --git a/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js b/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js index b57b090e68c..745a1206e6e 100644 --- a/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js +++ b/test/form/samples/property-setters-and-getters/early-access-getter-return/_expected.js @@ -1,7 +1,16 @@ function getReturnExpressionBeforeInit() { - { + const bar = { + [foo.value()]: true + }; + if (bar.baz) { console.log('retained'); } } +const foo = { + get value() { + return () => 'baz'; + } +}; + getReturnExpressionBeforeInit(); diff --git a/test/form/samples/recursive-calls/_expected.js b/test/form/samples/recursive-calls/_expected.js index b5e78683edd..ac8c402c4d1 100644 --- a/test/form/samples/recursive-calls/_expected.js +++ b/test/form/samples/recursive-calls/_expected.js @@ -1,3 +1,20 @@ +const removed4 = () => globalThis.unknown ? removed4() : { x: () => {} }; +removed4().x(); + +const removed6 = { + get x () { + return globalThis.unknown ? removed6.x : () => {}; + } +}; +removed6.x(); + +const removed8 = { + get x () { + return globalThis.unknown ? removed8.x : { y: () => {} }; + } +}; +removed8.x.y(); + const retained1 = () => globalThis.unknown ? retained1() : console.log( 'effect' ); retained1(); diff --git a/test/form/samples/side-effects-object-literal-calls/_expected/cjs.js b/test/form/samples/side-effects-object-literal-calls/_expected.js similarity index 83% rename from test/form/samples/side-effects-object-literal-calls/_expected/cjs.js rename to test/form/samples/side-effects-object-literal-calls/_expected.js index f46c6e0344f..5f7c1d32a3d 100644 --- a/test/form/samples/side-effects-object-literal-calls/_expected/cjs.js +++ b/test/form/samples/side-effects-object-literal-calls/_expected.js @@ -1,4 +1,8 @@ -'use strict'; +const removed1 = { x: () => {} }; +removed1.x(); + +const removed2 = { x: { y: () => {} } }; +removed2.x.y(); const retained1 = { x: () => {} }; retained1.y(); diff --git a/test/form/samples/side-effects-object-literal-calls/_expected/amd.js b/test/form/samples/side-effects-object-literal-calls/_expected/amd.js deleted file mode 100644 index 62791770a75..00000000000 --- a/test/form/samples/side-effects-object-literal-calls/_expected/amd.js +++ /dev/null @@ -1,25 +0,0 @@ -define(function () { 'use strict'; - - const retained1 = { x: () => {} }; - retained1.y(); - - const retained2 = { x: () => {} }; - retained2.x = {}; - retained2.x(); - - const retained3 = { x: { y: () => console.log('effect') } }; - const retained4 = { x: { y: {} } }; - retained4.x = retained3.x; - retained4.x.y(); - - const retained5 = { x: () => {} }; - const retained6 = retained5; - retained6.x = () => console.log('effect'); - retained5.x(); - - const retained7 = { x: { y: () => {} } }; - const retained8 = { x: retained7.x }; - retained8.x.y = () => console.log( 'effect' ); - retained7.x.y(); - -}); diff --git a/test/form/samples/side-effects-object-literal-calls/_expected/es.js b/test/form/samples/side-effects-object-literal-calls/_expected/es.js deleted file mode 100644 index fc8dcbfe778..00000000000 --- a/test/form/samples/side-effects-object-literal-calls/_expected/es.js +++ /dev/null @@ -1,21 +0,0 @@ -const retained1 = { x: () => {} }; -retained1.y(); - -const retained2 = { x: () => {} }; -retained2.x = {}; -retained2.x(); - -const retained3 = { x: { y: () => console.log('effect') } }; -const retained4 = { x: { y: {} } }; -retained4.x = retained3.x; -retained4.x.y(); - -const retained5 = { x: () => {} }; -const retained6 = retained5; -retained6.x = () => console.log('effect'); -retained5.x(); - -const retained7 = { x: { y: () => {} } }; -const retained8 = { x: retained7.x }; -retained8.x.y = () => console.log( 'effect' ); -retained7.x.y(); diff --git a/test/form/samples/side-effects-object-literal-calls/_expected/iife.js b/test/form/samples/side-effects-object-literal-calls/_expected/iife.js deleted file mode 100644 index cc2c085f035..00000000000 --- a/test/form/samples/side-effects-object-literal-calls/_expected/iife.js +++ /dev/null @@ -1,26 +0,0 @@ -(function () { - 'use strict'; - - const retained1 = { x: () => {} }; - retained1.y(); - - const retained2 = { x: () => {} }; - retained2.x = {}; - retained2.x(); - - const retained3 = { x: { y: () => console.log('effect') } }; - const retained4 = { x: { y: {} } }; - retained4.x = retained3.x; - retained4.x.y(); - - const retained5 = { x: () => {} }; - const retained6 = retained5; - retained6.x = () => console.log('effect'); - retained5.x(); - - const retained7 = { x: { y: () => {} } }; - const retained8 = { x: retained7.x }; - retained8.x.y = () => console.log( 'effect' ); - retained7.x.y(); - -}()); diff --git a/test/form/samples/side-effects-object-literal-calls/_expected/system.js b/test/form/samples/side-effects-object-literal-calls/_expected/system.js deleted file mode 100644 index 26d757a0f66..00000000000 --- a/test/form/samples/side-effects-object-literal-calls/_expected/system.js +++ /dev/null @@ -1,30 +0,0 @@ -System.register([], function () { - 'use strict'; - return { - execute: function () { - - const retained1 = { x: () => {} }; - retained1.y(); - - const retained2 = { x: () => {} }; - retained2.x = {}; - retained2.x(); - - const retained3 = { x: { y: () => console.log('effect') } }; - const retained4 = { x: { y: {} } }; - retained4.x = retained3.x; - retained4.x.y(); - - const retained5 = { x: () => {} }; - const retained6 = retained5; - retained6.x = () => console.log('effect'); - retained5.x(); - - const retained7 = { x: { y: () => {} } }; - const retained8 = { x: retained7.x }; - retained8.x.y = () => console.log( 'effect' ); - retained7.x.y(); - - } - }; -}); diff --git a/test/form/samples/side-effects-object-literal-calls/_expected/umd.js b/test/form/samples/side-effects-object-literal-calls/_expected/umd.js deleted file mode 100644 index 6e13cf5f6ee..00000000000 --- a/test/form/samples/side-effects-object-literal-calls/_expected/umd.js +++ /dev/null @@ -1,28 +0,0 @@ -(function (factory) { - typeof define === 'function' && define.amd ? define(factory) : - factory(); -}((function () { 'use strict'; - - const retained1 = { x: () => {} }; - retained1.y(); - - const retained2 = { x: () => {} }; - retained2.x = {}; - retained2.x(); - - const retained3 = { x: { y: () => console.log('effect') } }; - const retained4 = { x: { y: {} } }; - retained4.x = retained3.x; - retained4.x.y(); - - const retained5 = { x: () => {} }; - const retained6 = retained5; - retained6.x = () => console.log('effect'); - retained5.x(); - - const retained7 = { x: { y: () => {} } }; - const retained8 = { x: retained7.x }; - retained8.x.y = () => console.log( 'effect' ); - retained7.x.y(); - -}))); diff --git a/test/form/samples/side-effects-object-literal-mutation-misc/_config.js b/test/form/samples/side-effects-object-literal-mutation-misc/_config.js new file mode 100644 index 00000000000..d94d592cf65 --- /dev/null +++ b/test/form/samples/side-effects-object-literal-mutation-misc/_config.js @@ -0,0 +1,4 @@ +module.exports = { + description: 'detects side-effects when setting and/or calling properties on object literals', + options: { output: { name: 'bundle' } } +}; diff --git a/test/form/samples/side-effects-object-literal-mutation-misc/_expected.js b/test/form/samples/side-effects-object-literal-mutation-misc/_expected.js new file mode 100644 index 00000000000..3f385c224c0 --- /dev/null +++ b/test/form/samples/side-effects-object-literal-mutation-misc/_expected.js @@ -0,0 +1,43 @@ +((function() { + const obj = { + modify() { + this.modified = true; + } + }; + obj.modify(); + console.log(obj.modified ? "PASS1" : "FAIL1"); +})()); + + ((function() { + const obj = {}; + function modify() { + this.modified = true; + } + modify.call(obj); + console.log(obj.modified ? "PASS2" : "FAIL2"); +})()); + + ((function() { + const obj = {}; + obj.modify = modify; + function modify() { + this.modified = true; + } + obj.modify(); + console.log(obj.modified ? "PASS3" : "FAIL3"); +})()); + +{ + const axis = {}; + axis[getResult().axis] = 1; + if (axis.x) { + console.log('PASS4'); + } else { + console.log('FAIL4'); + } + function getResult() { + const result = {}; + result.axis = 'x'; + return result; + } +} diff --git a/test/form/samples/side-effects-object-literal-mutation-misc/main.js b/test/form/samples/side-effects-object-literal-mutation-misc/main.js new file mode 100644 index 00000000000..c78e5cf1c6b --- /dev/null +++ b/test/form/samples/side-effects-object-literal-mutation-misc/main.js @@ -0,0 +1,53 @@ +let flag = true; + +// invariant object literals can still be dropped +let run = { + test1: flag, + test2: true, + test3: flag, + test4: true, +}; + +run.test1 && (function() { + const obj = { + modify() { + this.modified = true; + } + }; + obj.modify(); + console.log(obj.modified ? "PASS1" : "FAIL1"); +})(); + +run.test2 && (function() { + const obj = {}; + function modify() { + this.modified = true; + } + modify.call(obj); + console.log(obj.modified ? "PASS2" : "FAIL2"); +})(); + +run.test3 && (function() { + const obj = {}; + obj.modify = modify; + function modify() { + this.modified = true; + } + obj.modify(); + console.log(obj.modified ? "PASS3" : "FAIL3"); +})(); + +if (run.test4) { + const axis = {}; + axis[getResult().axis] = 1; + if (axis.x) { + console.log('PASS4'); + } else { + console.log('FAIL4'); + } + function getResult() { + const result = {}; + result.axis = 'x'; + return result; + } +} diff --git a/test/function/samples/modify-object-via-this-a/_config.js b/test/function/samples/modify-object-via-this-a/_config.js new file mode 100644 index 00000000000..0c4423de923 --- /dev/null +++ b/test/function/samples/modify-object-via-this-a/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'tracks object modification via "this"' +}; diff --git a/test/function/samples/modify-object-via-this-a/main.js b/test/function/samples/modify-object-via-this-a/main.js new file mode 100644 index 00000000000..46c6d4b730e --- /dev/null +++ b/test/function/samples/modify-object-via-this-a/main.js @@ -0,0 +1,9 @@ +const obj = { + modify() { + this.modified = true; + } +}; + +obj.modify(); + +assert.strictEqual(obj.modified ? 'MODIFIED' : 'BROKEN', 'MODIFIED'); diff --git a/test/function/samples/modify-object-via-this-b/_config.js b/test/function/samples/modify-object-via-this-b/_config.js new file mode 100644 index 00000000000..0c4423de923 --- /dev/null +++ b/test/function/samples/modify-object-via-this-b/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'tracks object modification via "this"' +}; diff --git a/test/function/samples/modify-object-via-this-b/main.js b/test/function/samples/modify-object-via-this-b/main.js new file mode 100644 index 00000000000..e38362a67ab --- /dev/null +++ b/test/function/samples/modify-object-via-this-b/main.js @@ -0,0 +1,9 @@ +const obj = {}; + +function modify() { + this.modified = true; +} + +modify.call(obj); + +assert.strictEqual(obj.modified ? 'MODIFIED' : 'BROKEN', 'MODIFIED'); diff --git a/test/function/samples/modify-object-via-this-c/_config.js b/test/function/samples/modify-object-via-this-c/_config.js new file mode 100644 index 00000000000..0c4423de923 --- /dev/null +++ b/test/function/samples/modify-object-via-this-c/_config.js @@ -0,0 +1,3 @@ +module.exports = { + description: 'tracks object modification via "this"' +}; diff --git a/test/function/samples/modify-object-via-this-c/main.js b/test/function/samples/modify-object-via-this-c/main.js new file mode 100644 index 00000000000..12ab312f0fb --- /dev/null +++ b/test/function/samples/modify-object-via-this-c/main.js @@ -0,0 +1,11 @@ +const obj = {}; + +obj.modify = modify; + +function modify() { + this.modified = true; +} + +obj.modify(); + +assert.strictEqual(obj.modified ? 'MODIFIED' : 'BROKEN', 'MODIFIED');