1 /* 2 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary Test the --add-reads option 27 * @library /tools/lib 28 * @modules jdk.compiler/com.sun.tools.javac.api 29 * jdk.compiler/com.sun.tools.javac.main 30 * jdk.jdeps/com.sun.tools.javap 31 * java.desktop 32 * @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask toolbox.JavapTask ModuleTestBase 33 * @run main AddReadsTest 34 */ 35 36 import java.nio.file.Files; 37 import java.nio.file.Path; 38 import java.util.Set; 39 40 import javax.annotation.processing.AbstractProcessor; 41 import javax.annotation.processing.RoundEnvironment; 42 import javax.annotation.processing.SupportedAnnotationTypes; 43 import javax.lang.model.SourceVersion; 44 import javax.lang.model.element.ModuleElement; 45 import javax.lang.model.element.ModuleElement.RequiresDirective; 46 import javax.lang.model.element.TypeElement; 47 import javax.lang.model.util.ElementFilter; 48 49 import toolbox.JarTask; 50 import toolbox.JavacTask; 51 import toolbox.JavapTask; 52 import toolbox.Task; 53 54 public class AddReadsTest extends ModuleTestBase { 55 main(String... args)56 public static void main(String... args) throws Exception { 57 new AddReadsTest().runTests(); 58 } 59 60 @Test testAddReads(Path base)61 public void testAddReads(Path base) throws Exception { 62 Path src = base.resolve("src"); 63 Path src_m1 = src.resolve("m1x"); 64 tb.writeJavaFiles(src_m1, 65 "module m1x { exports api; }", 66 "package api; public class Api { }"); 67 Path src_m2 = src.resolve("m2x"); 68 tb.writeJavaFiles(src_m2, 69 "module m2x { }", 70 "package test; public class Test extends api.Api { }"); 71 Path classes = base.resolve("classes"); 72 tb.createDirectories(classes); 73 74 String log = new JavacTask(tb) 75 .options("-XDrawDiagnostics", 76 "--module-source-path", src.toString()) 77 .outdir(classes) 78 .files(findJavaFiles(src)) 79 .run(Task.Expect.FAIL) 80 .writeAll() 81 .getOutput(Task.OutputKind.DIRECT); 82 83 checkOutputContains(log, 84 "Test.java:1:41: compiler.err.package.not.visible: api, (compiler.misc.not.def.access.does.not.read: m2x, api, m1x)"); 85 86 //test add dependencies: 87 new JavacTask(tb) 88 .options("--add-reads", "m2x=m1x", 89 "--module-source-path", src.toString(), 90 "-processor", VerifyRequires.class.getName()) 91 .outdir(classes) 92 .files(findJavaFiles(src)) 93 .run() 94 .writeAll(); 95 96 String decompiled = new JavapTask(tb) 97 .options("-verbose", 98 classes.resolve("m2x").resolve("module-info.class").toString()) 99 .run() 100 .getOutput(Task.OutputKind.DIRECT); 101 102 if (decompiled.contains("m1x")) { 103 throw new Exception("Incorrectly refers to m1x module."); 104 } 105 106 //cyclic dependencies OK when created through addReads: 107 new JavacTask(tb) 108 .options("--add-reads", "m2x=m1x", 109 "--add-reads", "m1x=m2x", 110 "--module-source-path", src.toString()) 111 .outdir(classes) 112 .files(findJavaFiles(src)) 113 .run() 114 .writeAll(); 115 116 tb.writeJavaFiles(src_m2, 117 "module m2x { requires m1x; }"); 118 119 new JavacTask(tb) 120 .options("--add-reads", "m1x=m2x", 121 "--module-source-path", src.toString()) 122 .outdir(classes) 123 .files(findJavaFiles(src)) 124 .run() 125 .writeAll(); 126 } 127 128 @SupportedAnnotationTypes("*") 129 public static final class VerifyRequires extends AbstractProcessor { 130 131 @Override process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)132 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 133 ModuleElement m2Module = processingEnv.getElementUtils().getModuleElement("m2x"); 134 if (m2Module == null) { 135 throw new AssertionError("Cannot find the m2x module!"); 136 } 137 boolean foundM1 = false; 138 for (RequiresDirective rd : ElementFilter.requiresIn(m2Module.getDirectives())) { 139 foundM1 |= rd.getDependency().getSimpleName().contentEquals("m1x"); 140 } 141 if (!foundM1) { 142 throw new AssertionError("Cannot find the dependency on m1x module!"); 143 } 144 return false; 145 } 146 147 @Override getSupportedSourceVersion()148 public SourceVersion getSupportedSourceVersion() { 149 return SourceVersion.latest(); 150 } 151 152 } 153 154 @Test testAddReadsUnnamedModule(Path base)155 public void testAddReadsUnnamedModule(Path base) throws Exception { 156 Path jar = prepareTestJar(base); 157 158 Path moduleSrc = base.resolve("module-src"); 159 Path m1 = moduleSrc.resolve("m1x"); 160 161 Path classes = base.resolve("classes"); 162 163 Files.createDirectories(classes); 164 165 tb.writeJavaFiles(m1, 166 "module m1x { }", 167 "package impl; public class Impl { api.Api api; }"); 168 169 new JavacTask(tb) 170 .options("--class-path", jar.toString(), 171 "--add-reads", "m1x=ALL-UNNAMED", 172 "-XDrawDiagnostics") 173 .outdir(classes) 174 .files(findJavaFiles(moduleSrc)) 175 .run() 176 .writeAll(); 177 } 178 179 @Test testAddReadsUnnamedModulePackageConflict(Path base)180 public void testAddReadsUnnamedModulePackageConflict(Path base) throws Exception { 181 Path jar = prepareTestJar(base); 182 183 Path moduleSrc = base.resolve("module-src"); 184 Path m1 = moduleSrc.resolve("m1x"); 185 186 Path classes = base.resolve("classes"); 187 188 Files.createDirectories(classes); 189 190 tb.writeJavaFiles(m1, 191 "module m1x { }", 192 "package api; public class Api { public static void test() { } }", 193 "package impl; public class Impl { { api.Api.test(); } }"); 194 195 new JavacTask(tb) 196 .options("--class-path", jar.toString(), 197 "--module-source-path", moduleSrc.toString(), 198 "--add-reads", "m1x=ALL-UNNAMED", 199 "-XDrawDiagnostics") 200 .outdir(classes) 201 .files(m1.resolve("impl").resolve("Impl.java")) 202 .run() 203 .writeAll(); 204 } 205 206 @Test testAddReadsUnnamedToJavaBase(Path base)207 public void testAddReadsUnnamedToJavaBase(Path base) throws Exception { 208 Path jar = prepareTestJar(base); 209 Path src = base.resolve("src"); 210 Path classes = base.resolve("classes"); 211 212 Files.createDirectories(classes); 213 214 tb.writeJavaFiles(src, 215 "package impl; public class Impl { api.Api a; }"); 216 217 new JavacTask(tb) 218 .options("--class-path", jar.toString(), 219 "--add-reads", "java.base=ALL-UNNAMED", 220 "--patch-module", "java.base=" + src) 221 .outdir(classes) 222 .files(src.resolve("impl").resolve("Impl.java")) 223 .run() 224 .writeAll(); 225 } 226 227 @Test testAddReadsToJavaBase(Path base)228 public void testAddReadsToJavaBase(Path base) throws Exception { 229 Path src = base.resolve("src"); 230 Path classes = base.resolve("classes"); 231 232 Files.createDirectories(classes); 233 234 tb.writeJavaFiles(src, 235 "package impl; public class Impl { javax.swing.JButton b; }"); 236 237 new JavacTask(tb) 238 .options("--add-modules", "java.desktop", 239 "--add-reads", "java.base=java.desktop", 240 "--patch-module", "java.base=" + src) 241 .outdir(classes) 242 .files(findJavaFiles(src)) 243 .run() 244 .writeAll(); 245 } 246 prepareTestJar(Path base)247 private Path prepareTestJar(Path base) throws Exception { 248 Path legacySrc = base.resolve("legacy-src"); 249 tb.writeJavaFiles(legacySrc, 250 "package api; public abstract class Api {}"); 251 Path legacyClasses = base.resolve("legacy-classes"); 252 Files.createDirectories(legacyClasses); 253 254 String log = new JavacTask(tb) 255 .options() 256 .outdir(legacyClasses) 257 .files(findJavaFiles(legacySrc)) 258 .run() 259 .writeAll() 260 .getOutput(Task.OutputKind.DIRECT); 261 262 if (!log.isEmpty()) { 263 throw new Exception("unexpected output: " + log); 264 } 265 266 Path lib = base.resolve("lib"); 267 268 Files.createDirectories(lib); 269 270 Path jar = lib.resolve("test-api-1.0.jar"); 271 272 new JarTask(tb, jar) 273 .baseDir(legacyClasses) 274 .files("api/Api.class") 275 .run(); 276 277 return jar; 278 } 279 280 @Test testX(Path base)281 public void testX(Path base) throws Exception { 282 Path src = base.resolve("src"); 283 Path src_m1 = src.resolve("m1x"); 284 tb.writeJavaFiles(src_m1, 285 "module m1x { provides java.lang.Runnable with impl.Impl; }", 286 "package impl; public class Impl implements Runnable { public void run() { } }"); 287 Path classes = base.resolve("classes"); 288 tb.createDirectories(classes); 289 290 new JavacTask(tb) 291 .options("--module-source-path", src.toString()) 292 .outdir(classes) 293 .files(findJavaFiles(src)) 294 .run() 295 .writeAll(); 296 297 Path unnamedSrc = base.resolve("unnamed-src"); 298 Path unnamedClasses = base.resolve("unnamed-classes"); 299 300 Files.createDirectories(unnamedClasses); 301 302 tb.writeJavaFiles(unnamedSrc, 303 "package impl; public class Impl { }"); 304 305 new JavacTask(tb) 306 .options("--add-reads", "m1x=ALL-UNNAMED", 307 "--patch-module", "m1x=" + unnamedSrc, 308 "--module-path", classes.toString()) 309 .outdir(unnamedClasses) 310 .files(findJavaFiles(unnamedSrc)) 311 .run() 312 .writeAll(); 313 } 314 315 @Test testAddSelf(Path base)316 public void testAddSelf(Path base) throws Exception { 317 Path src = base.resolve("src"); 318 Path src_m1 = src.resolve("m1x"); 319 tb.writeJavaFiles(src_m1, 320 "module m1x { exports p1; }", 321 "package p1; public class C1 { }"); 322 Path classes = base.resolve("classes"); 323 tb.createDirectories(classes); 324 325 new JavacTask(tb) 326 .options("--module-source-path", src.toString(), 327 "--add-reads", "m1x=m1x") 328 .outdir(classes) 329 .files(findJavaFiles(src)) 330 .run() 331 .writeAll(); 332 } 333 334 @Test testEmpty(Path base)335 public void testEmpty(Path base) throws Exception { 336 Path src = base.resolve("src"); 337 tb.writeJavaFiles(src, "class Dummy { }"); 338 Path classes = base.resolve("classes"); 339 tb.createDirectories(classes); 340 341 testEmpty(src, classes, "--add-reads", ""); 342 testEmpty(src, classes, "--add-reads="); 343 } 344 testEmpty(Path src, Path classes, String... options)345 private void testEmpty(Path src, Path classes, String... options) throws Exception { 346 String log = new JavacTask(tb, Task.Mode.CMDLINE) 347 .options(options) 348 .outdir(classes) 349 .files(findJavaFiles(src)) 350 .run(Task.Expect.FAIL) 351 .writeAll() 352 .getOutput(Task.OutputKind.DIRECT); 353 354 checkOutputContains(log, 355 "error: no value for --add-reads option"); 356 } 357 358 @Test testEmptyItem(Path base)359 public void testEmptyItem(Path base) throws Exception { 360 Path src = base.resolve("src"); 361 Path src_m1 = src.resolve("m1x"); 362 tb.writeJavaFiles(src_m1, 363 "module m1x { exports p1; }", 364 "package p1; public class C1 { }"); 365 Path src_m2 = src.resolve("m2x"); 366 tb.writeJavaFiles(src_m2, 367 "module m2x { }", 368 "package p2; class C2 { }"); 369 Path src_m3 = src.resolve("m3x"); 370 tb.writeJavaFiles(src_m3, 371 "module m3x { }", 372 "package p3; class C3 { p1.C1 c1; }"); 373 Path classes = base.resolve("classes"); 374 tb.createDirectories(classes); 375 376 testEmptyItem(src, classes, "m3x=,m1x"); 377 testEmptyItem(src, classes, "m3x=m1x,,m2x"); 378 testEmptyItem(src, classes, "m3x=m1x,"); 379 } 380 testEmptyItem(Path src, Path classes, String option)381 private void testEmptyItem(Path src, Path classes, String option) throws Exception { 382 new JavacTask(tb) 383 .options("--module-source-path", src.toString(), 384 "--add-reads", option) 385 .outdir(classes) 386 .files(findJavaFiles(src)) 387 .run() 388 .writeAll(); 389 } 390 391 @Test testEmptyList(Path base)392 public void testEmptyList(Path base) throws Exception { 393 Path src = base.resolve("src"); 394 Path src_m1 = src.resolve("m1x"); 395 tb.writeJavaFiles(src_m1, 396 "module m1x { exports p1; }", 397 "package p1; public class C1 { }"); 398 Path src_m2 = src.resolve("m2x"); 399 tb.writeJavaFiles(src_m2, 400 "module m2x { }", 401 "package p2; class C2 { }"); 402 Path src_m3 = src.resolve("m3x"); 403 tb.writeJavaFiles(src_m3, 404 "module m3x { }", 405 "package p3; class C3 { p1.C1 c1; }"); 406 Path classes = base.resolve("classes"); 407 tb.createDirectories(classes); 408 409 testEmptyList(src, classes, "m3x="); 410 testEmptyList(src, classes, "m3x=,"); 411 } 412 testEmptyList(Path src, Path classes, String option)413 private void testEmptyList(Path src, Path classes, String option) throws Exception { 414 String log = new JavacTask(tb, Task.Mode.CMDLINE) 415 .options("--module-source-path", src.toString(), 416 "--add-reads", option) 417 .outdir(classes) 418 .files(findJavaFiles(src)) 419 .run(Task.Expect.FAIL) 420 .writeAll() 421 .getOutput(Task.OutputKind.DIRECT); 422 423 checkOutputContains(log, 424 "error: bad value for --add-reads option: '" + option + "'"); 425 } 426 427 @Test testMultipleAddReads_DifferentModules(Path base)428 public void testMultipleAddReads_DifferentModules(Path base) throws Exception { 429 Path src = base.resolve("src"); 430 Path src_m1 = src.resolve("m1x"); 431 tb.writeJavaFiles(src_m1, 432 "module m1x { exports p1; }", 433 "package p1; public class C1 { }"); 434 Path src_m2 = src.resolve("m2x"); 435 tb.writeJavaFiles(src_m2, 436 "module m2x { }", 437 "package p2; class C2 { p1.C1 c1; }"); 438 Path src_m3 = src.resolve("m3x"); 439 tb.writeJavaFiles(src_m3, 440 "module m3x { }", 441 "package p3; class C3 { p1.C1 c1; }"); 442 Path classes = base.resolve("classes"); 443 tb.createDirectories(classes); 444 445 new JavacTask(tb) 446 .options("--module-source-path", src.toString(), 447 "--add-reads", "m2x=m1x", 448 "--add-reads", "m3x=m1x") 449 .outdir(classes) 450 .files(findJavaFiles(src)) 451 .run() 452 .writeAll(); 453 } 454 455 @Test testMultipleAddReads_SameModule(Path base)456 public void testMultipleAddReads_SameModule(Path base) throws Exception { 457 Path src = base.resolve("src"); 458 Path src_m1 = src.resolve("m1x"); 459 tb.writeJavaFiles(src_m1, 460 "module m1x { exports p1; }", 461 "package p1; public class C1 { }"); 462 Path src_m2 = src.resolve("m2x"); 463 tb.writeJavaFiles(src_m2, 464 "module m2x { exports p2; }", 465 "package p2; public class C2 { }"); 466 Path src_m3 = src.resolve("m3x"); 467 tb.writeJavaFiles(src_m3, 468 "module m3x { }", 469 "package p3; class C3 { p1.C1 c1; p2.C2 c2; }"); 470 Path classes = base.resolve("classes"); 471 tb.createDirectories(classes); 472 473 new JavacTask(tb) 474 .options("--module-source-path", src.toString(), 475 "--add-reads", "m3x=m1x", 476 "--add-reads", "m3x=m2x") 477 .outdir(classes) 478 .files(findJavaFiles(src)) 479 .run() 480 .writeAll(); 481 } 482 483 @Test testDuplicateAddReads_SameOption(Path base)484 public void testDuplicateAddReads_SameOption(Path base) throws Exception { 485 Path src = base.resolve("src"); 486 Path src_m1 = src.resolve("m1x"); 487 tb.writeJavaFiles(src_m1, 488 "module m1x { exports p1; }", 489 "package p1; public class C1 { }"); 490 Path src_m2 = src.resolve("m2x"); 491 tb.writeJavaFiles(src_m2, 492 "module m2x { exports p2; }", 493 "package p2; class C2 { p1.C1 c1; }"); 494 Path classes = base.resolve("classes"); 495 tb.createDirectories(classes); 496 497 new JavacTask(tb) 498 .options("--module-source-path", src.toString(), 499 "--add-reads", "m2x=m1x,m1x") 500 .outdir(classes) 501 .files(findJavaFiles(src)) 502 .run() 503 .writeAll(); 504 } 505 506 @Test testDuplicateAddReads_MultipleOptions(Path base)507 public void testDuplicateAddReads_MultipleOptions(Path base) throws Exception { 508 Path src = base.resolve("src"); 509 Path src_m1 = src.resolve("m1x"); 510 tb.writeJavaFiles(src_m1, 511 "module m1x { exports p1; }", 512 "package p1; public class C1 { }"); 513 Path src_m2 = src.resolve("m2x"); 514 tb.writeJavaFiles(src_m2, 515 "module m2x { }", 516 "package p2; class C2 { p1.C1 c1; }"); 517 Path classes = base.resolve("classes"); 518 tb.createDirectories(classes); 519 520 new JavacTask(tb) 521 .options("--module-source-path", src.toString(), 522 "--add-reads", "m2x=m1x", 523 "--add-reads", "m2x=m1x") 524 .outdir(classes) 525 .files(findJavaFiles(src)) 526 .run() 527 .writeAll(); 528 } 529 530 @Test testRepeatedAddReads(Path base)531 public void testRepeatedAddReads(Path base) throws Exception { 532 Path src = base.resolve("src"); 533 Path src_m1 = src.resolve("m1x"); 534 tb.writeJavaFiles(src_m1, 535 "module m1x { exports p1; }", 536 "package p1; public class C1 { }"); 537 Path src_m2 = src.resolve("m2x"); 538 tb.writeJavaFiles(src_m2, 539 "module m2x { exports p2; }", 540 "package p2; public class C2 { }"); 541 Path src_m3 = src.resolve("m3x"); 542 tb.writeJavaFiles(src_m3, 543 "module m3x { }", 544 "package p3; class C3 { p1.C1 c1; p2.C2 c2; }"); 545 Path classes = base.resolve("classes"); 546 tb.createDirectories(classes); 547 548 new JavacTask(tb) 549 .options("--module-source-path", src.toString(), 550 "--add-reads", "m3x=m1x", 551 "--add-reads", "m3x=m2x") 552 .outdir(classes) 553 .files(findJavaFiles(src)) 554 .run() 555 .writeAll(); 556 } 557 558 @Test testNoEquals(Path base)559 public void testNoEquals(Path base) throws Exception { 560 Path src = base.resolve("src"); 561 tb.writeJavaFiles(src, "class Dummy { }"); 562 Path classes = base.resolve("classes"); 563 tb.createDirectories(classes); 564 565 String log = new JavacTask(tb, Task.Mode.CMDLINE) 566 .options("-XDrawDiagnostics", 567 "--add-reads", "m1x:m2x") 568 .outdir(classes) 569 .files(findJavaFiles(src)) 570 .run(Task.Expect.FAIL) 571 .writeAll() 572 .getOutput(Task.OutputKind.DIRECT); 573 574 checkOutputContains(log, 575 "error: bad value for --add-reads option: 'm1x:m2x'"); 576 } 577 578 @Test testBadSourceName(Path base)579 public void testBadSourceName(Path base) throws Exception { 580 Path src = base.resolve("src"); 581 tb.writeJavaFiles(src, "class Dummy { }"); 582 Path classes = base.resolve("classes"); 583 tb.createDirectories(classes); 584 585 String log = new JavacTask(tb) 586 .options("-XDrawDiagnostics", 587 "--add-reads", "bad*Source=m2x") 588 .outdir(classes) 589 .files(findJavaFiles(src)) 590 .run() 591 .writeAll() 592 .getOutput(Task.OutputKind.DIRECT); 593 594 checkOutputContains(log, 595 "- compiler.warn.bad.name.for.option: --add-reads, bad*Source"); 596 } 597 598 @Test testBadTargetName(Path base)599 public void testBadTargetName(Path base) throws Exception { 600 Path src = base.resolve("src"); 601 Path src_m1 = src.resolve("m1x"); 602 tb.writeJavaFiles(src_m1, 603 "module m1x { }", 604 "package p1; class C1 { }"); 605 Path classes = base.resolve("classes"); 606 tb.createDirectories(classes); 607 608 String log = new JavacTask(tb) 609 .options("-XDrawDiagnostics", 610 "--add-reads", "m1x=badTarget!") 611 .outdir(classes) 612 .files(findJavaFiles(src)) 613 .run() 614 .writeAll() 615 .getOutput(Task.OutputKind.DIRECT); 616 617 checkOutputContains(log, 618 "- compiler.warn.bad.name.for.option: --add-reads, badTarget!"); 619 } 620 621 @Test testSourceNameNotFound(Path base)622 public void testSourceNameNotFound(Path base) throws Exception { 623 Path src = base.resolve("src"); 624 Path src_m1 = src.resolve("m1x"); 625 tb.writeJavaFiles(src_m1, 626 "module m1x { exports p1; }", 627 "package p1; public class C1 { }"); 628 Path classes = base.resolve("classes"); 629 tb.createDirectories(classes); 630 631 String log = new JavacTask(tb) 632 .options("-XDrawDiagnostics", 633 "--add-reads", "missingSource=m") 634 .outdir(classes) 635 .files(findJavaFiles(src)) 636 .run() 637 .writeAll() 638 .getOutput(Task.OutputKind.DIRECT); 639 640 checkOutputContains(log, 641 "- compiler.warn.module.for.option.not.found: --add-reads, missingSource"); 642 } 643 644 @Test testTargetNameNotFound(Path base)645 public void testTargetNameNotFound(Path base) throws Exception { 646 Path src = base.resolve("src"); 647 Path src_m1 = src.resolve("m1x"); 648 tb.writeJavaFiles(src_m1, 649 "module m1x { exports p1; }", 650 "package p1; public class C1 { }"); 651 Path classes = base.resolve("classes"); 652 tb.createDirectories(classes); 653 654 String log = new JavacTask(tb) 655 .options("-XDrawDiagnostics", 656 "--add-reads", "m1x=missingTarget") 657 .outdir(classes) 658 .files(findJavaFiles(src)) 659 .run() 660 .writeAll() 661 .getOutput(Task.OutputKind.DIRECT); 662 663 checkOutputContains(log, 664 "- compiler.warn.module.for.option.not.found: --add-reads, missingTarget"); 665 } 666 } 667